diff --git a/.clippy.toml b/.clippy.toml new file mode 100644 index 000000000000..4296655a040f --- /dev/null +++ b/.clippy.toml @@ -0,0 +1 @@ +allow-dbg-in-tests = true diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e66da85d6c78..dc4010d0fd0c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -45,6 +45,10 @@ crates/test_utils/tests/connectors/ @juspay/hyperswitch-connector crates/test_utils/tests/sample_auth.toml @juspay/hyperswitch-connector crates/connector_configs/ @juspay/hyperswitch-connector crates/hyperswitch_connectors/ @juspay/hyperswitch-connector +crates/api_models/src/connector_enums.rs @juspay/hyperswitch-connector +crates/common_enums/src/connector_enums.rs @juspay/hyperswitch-connector +crates/router/src/configs/defaults/payment_connector_required_fields.rs @juspay/hyperswitch-connector +crates/hyperswitch_interfaces/src/configs.rs @juspay/hyperswitch-connector crates/router/src/compatibility/ @juspay/hyperswitch-compatibility @@ -56,6 +60,7 @@ crates/euclid @juspay/hyperswitch-routing crates/euclid_macros @juspay/hyperswitch-routing crates/euclid_wasm @juspay/hyperswitch-routing crates/kgraph_utils @juspay/hyperswitch-routing +crates/pm_auth @juspay/hyperswitch-routing crates/router/src/routes/routing.rs @juspay/hyperswitch-routing crates/router/src/core/routing @juspay/hyperswitch-routing crates/router/src/core/routing.rs @juspay/hyperswitch-routing diff --git a/.github/data/cards_info.csv b/.github/data/cards_info.csv new file mode 100644 index 000000000000..4e7a19e4b232 --- /dev/null +++ b/.github/data/cards_info.csv @@ -0,0 +1,13 @@ +card_iin,card_issuer,card_network,card_type,card_subtype,card_issuing_country,bank_code_id,bank_code,country_code,date_created,last_updated,last_updated_provider +111122,,Visa,DEBIT,,,,,,2017-06-18 10:00:39,, +400000,INTL HDQTRS-CENTER OWNED,Visa,CREDIT,,UNITEDSTATES,,,840,2015-08-04 08:50:42,2022-06-30 16:54:07,Visa +401200,VISA PRODUCTION SUPPORT CLIENT BID 1,Visa,DEBIT,CLASSIC,UNITEDSTATES,,,840,2015-07-27 12:04:10,2022-07-06 10:24:32,Visa +411111,JP Morgan,Visa,CREDIT,,INDIA,131,JP_JPM,,2015-07-22 23:36:35,2021-02-23 07:38:19, +420000,JP Morgan,Visa,CREDIT,,UNITEDSTATES,131,JP_JPM,,2016-05-12 18:37:38,2021-02-23 07:56:53, +424242,STRIPE PAYMENTS UK LIMITED,Visa,CREDIT,,UNITEDKINGDOM,,,826,2015-07-22 16:41:32,2022-12-12 13:02:19,Visa +434994,INTESA SANPAOLO S.P.A.,Visa,CREDIT,CLASSIC,ITALY,,,380,2015-12-30 17:01:46,2022-12-12 13:03:12,Visa +444409,SELECT SEVEN FEDERAL CREDIT UNION,Visa,DEBIT,CLASSIC,UNITEDSTATES,,,840,2016-05-12 18:41:09,2022-07-06 10:26:20,Visa +486871,AMERICA FIRST FEDERAL CREDIT UNION,Visa,CREDIT,BUSINESS,UNITEDSTATES,,,840,2016-05-12 18:47:21,2022-07-06 10:35:26,Visa +491761,BANKPOLSKAKASAOPIEKIS.A.(BANKPEKAOSA),Visa,CREDIT,BUSINESS,POLAND,,,,2015-09-07 12:58:50,, +510510,BANKOFHAWAII,Mastercard,CREDIT,,UNITEDSTATES,,,,2015-07-30 05:18:28,, +520474,MASTERCARD INTERNATIONAL,Visa,DEBIT,,UNITEDSTATES,,,840,2016-05-12 18:51:16,2022-12-12 15:17:33,Visa diff --git a/.github/workflows/CI-pr.yml b/.github/workflows/CI-pr.yml index 220c4f2577c2..369da734da5a 100644 --- a/.github/workflows/CI-pr.yml +++ b/.github/workflows/CI-pr.yml @@ -325,7 +325,7 @@ jobs: - name: Run cargo clippy with v2 features enabled shell: bash - run: just clippy_v2 -- -D warnings -Aunused -Aclippy::todo -Aclippy::diverging_sub_expression + run: just clippy_v2 - name: Run cargo check enabling only the release and v2 features shell: bash diff --git a/.github/workflows/cypress-tests-runner.yml b/.github/workflows/cypress-tests-runner.yml index 3a10dceb37e2..bc83c2388f7e 100644 --- a/.github/workflows/cypress-tests-runner.yml +++ b/.github/workflows/cypress-tests-runner.yml @@ -13,7 +13,8 @@ concurrency: env: CARGO_INCREMENTAL: 0 CARGO_NET_RETRY: 10 - CONNECTORS: stripe + PAYMENTS_CONNECTORS: "cybersource stripe" + PAYOUTS_CONNECTORS: "adyenplatform wise" RUST_BACKTRACE: short RUSTUP_MAX_RETRIES: 10 RUN_TESTS: ${{ ((github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name)) || (github.event_name == 'merge_group')}} @@ -21,9 +22,68 @@ env: RUST_MIN_STACK: 10485760 jobs: + formatter: + name: Run formatter on Cypress tests and address lints + if: ${{ github.event_name == 'pull_request' }} + runs-on: ubuntu-latest + steps: + - name: Generate a token + if: ${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }} + id: generate_token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.HYPERSWITCH_BOT_APP_ID }} + private-key: ${{ secrets.HYPERSWITCH_BOT_APP_PRIVATE_KEY }} + + - name: Checkout repository with token + if: ${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }} + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.ref }} + token: ${{ steps.generate_token.outputs.token }} + + - name: Checkout repository for fork + if: ${{ github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name }} + uses: actions/checkout@v4 + + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install Cypress and dependencies + run: | + npm ci --prefix ./cypress-tests + + - name: Check formatting for forked pull requests + if: ${{ github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name }} + shell: bash + run: | + npm run format:check --prefix cypress-tests + npm run lint --prefix cypress-tests + + - name: Check formatting + if: ${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }} + shell: bash + run: | + npm run format --prefix cypress-tests + npm run lint --prefix cypress-tests -- --fix + + if ! git diff --exit-code --quiet -- cypress-tests; then + echo "::notice::Cypress formatting and lint check failed" + + git config --local user.name 'hyperswitch-bot[bot]' + git config --local user.email '148525504+hyperswitch-bot[bot]@users.noreply.github.com' + + git add cypress-tests + git commit --message 'chore(cypress): run formatter and address lints' + git push + fi + runner: name: Run Cypress tests - runs-on: hyperswitch-runners + runs-on: ubuntu-latest services: redis: @@ -68,7 +128,7 @@ jobs: CONNECTOR_AUTH_PASSPHRASE: ${{ secrets.CONNECTOR_AUTH_PASSPHRASE }} CONNECTOR_CREDS_S3_BUCKET_URI: ${{ secrets.CONNECTOR_CREDS_S3_BUCKET_URI}} DESTINATION_FILE_NAME: "creds.json.gpg" - S3_SOURCE_FILE_NAME: "f64157fe-a8f7-43a8-a268-b17e9a8c305f.json.gpg" + S3_SOURCE_FILE_NAME: "aa328308-b34e-41b7-a590-4fe45cfe7991.json.gpg" shell: bash run: | mkdir -p ".github/secrets" ".github/test" @@ -151,9 +211,14 @@ jobs: DATABASE_URL: postgres://db_user:db_pass@localhost:5432/hyperswitch_db run: just migrate run --locked-schema + - name: Insert card info into the database + if: ${{ env.RUN_TESTS == 'true' }} + run: | + PGPASSWORD=db_pass psql --host=localhost --port=5432 --username=db_user --dbname=hyperswitch_db --command "\copy cards_info FROM '.github/data/cards_info.csv' DELIMITER ',' CSV HEADER;" + - name: Build project if: ${{ env.RUN_TESTS == 'true' }} - run: cargo build --package router --bin router --jobs 6 + run: cargo build --package router --bin router --jobs 3 - name: Setup Local Server if: ${{ env.RUN_TESTS == 'true' }} @@ -181,29 +246,10 @@ jobs: if: ${{ env.RUN_TESTS == 'true' }} env: CYPRESS_BASEURL: "http://localhost:8080" + ROUTER__SERVER__WORKERS: 4 shell: bash -leuo pipefail {0} run: | - cd cypress-tests - - RED='\033[0;31m' - RESET='\033[0m' - - failed_connectors=() - - for connector in $(echo "${CONNECTORS}" | tr "," "\n"); do - echo "${connector}" - for service in "payments" "payouts"; do - if ! ROUTER__SERVER__WORKERS=4 CYPRESS_CONNECTOR="${connector}" npm run cypress:"${service}"; then - failed_connectors+=("${connector}-${service}") - fi - done - done - - if [ ${#failed_connectors[@]} -gt 0 ]; then - echo -e "${RED}One or more connectors failed to run:${RESET}" - printf '%s\n' "${failed_connectors[@]}" - exit 1 - fi + scripts/execute_cypress.sh kill "${{ env.PID }}" @@ -213,6 +259,5 @@ jobs: with: name: cypress-test-results path: | - cypress-tests/cypress/reports/*.json - cypress-tests/cypress/reports/*.html + cypress-tests/cypress/reports/ retention-days: 1 diff --git a/.github/workflows/postman-collection-runner.yml b/.github/workflows/postman-collection-runner.yml index de8235c14ea4..b8ce65f4b6c8 100644 --- a/.github/workflows/postman-collection-runner.yml +++ b/.github/workflows/postman-collection-runner.yml @@ -17,7 +17,7 @@ env: CONNECTORS: stripe RUST_BACKTRACE: short RUSTUP_MAX_RETRIES: 10 - RUST_MIN_STACK: 8388608 + RUST_MIN_STACK: 10485760 jobs: runner: diff --git a/.github/workflows/wasm-bulild-check.yml b/.github/workflows/wasm-bulild-check.yml new file mode 100644 index 000000000000..d9b2860a361e --- /dev/null +++ b/.github/workflows/wasm-bulild-check.yml @@ -0,0 +1,34 @@ +name: CI wasm-check + +on: + push: + branches: + - main + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + wasm-ci: + name: Check wasm build + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: stable 2 weeks ago + + - name: Install wasm-pack + uses: taiki-e/install-action@v2 + with: + tool: wasm-pack + checksum: true + + - name: wasm build + shell: bash + run: make euclid-wasm diff --git a/.gitignore b/.gitignore index 1aa3faf2c1d1..dcbeddf7adab 100644 --- a/.gitignore +++ b/.gitignore @@ -187,7 +187,7 @@ target/ ### VisualStudioCode ### .vscode/* -!.vscode/settings.json +.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json @@ -263,4 +263,9 @@ result* node_modules/ # cypress credentials -creds.json \ No newline at end of file +creds.json + +/.direnv + +# Nix services data +/data diff --git a/.typos.toml b/.typos.toml index 79c86a39c6b5..983ead3f75d6 100644 --- a/.typos.toml +++ b/.typos.toml @@ -1,5 +1,8 @@ [default] check-filename = true +extend-ignore-identifiers-re = [ + "UE_[0-9]{3,4}", # Unified error codes +] [default.extend-identifiers] ABD = "ABD" # Aberdeenshire, UK ISO 3166-2 code @@ -16,6 +19,7 @@ HypoNoeLbFurNiederosterreichUWien = "HypoNoeLbFurNiederosterreichUWien" hypo_noe_lb_fur_niederosterreich_u_wien = "hypo_noe_lb_fur_niederosterreich_u_wien" IOT = "IOT" # British Indian Ocean Territory country code klick = "klick" # Swedish word for clicks +FPR = "FPR" # Fraud Prevention Rules LSO = "LSO" # Lesotho country code NAM = "NAM" # Namibia country code ND = "ND" # North Dakota state code @@ -38,8 +42,7 @@ ws2ipdef = "ws2ipdef" # WinSock Extension ws2tcpip = "ws2tcpip" # WinSock Extension ZAR = "ZAR" # South African Rand currency code JOD = "JOD" # Jordan currency code -UE_000 = "UE_000" #default unified error code - +Payed = "Payed" # Paid status for digital virgo [default.extend-words] aci = "aci" # Name of a connector diff --git a/CHANGELOG.md b/CHANGELOG.md index d706486e9209..2bc0977570e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,1256 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.12.26.1 + +### Bug Fixes + +- **router:** Populate `profile_id` in for the HeaderAuth of v1 ([#6936](https://github.com/juspay/hyperswitch/pull/6936)) ([`10a4337`](https://github.com/juspay/hyperswitch/commit/10a43370e8b6f2f14850a505f89796e7accffcec)) + +### Documentation + +- **openapi:** Update /relay request example ([#6942](https://github.com/juspay/hyperswitch/pull/6942)) ([`d849403`](https://github.com/juspay/hyperswitch/commit/d849403460f338f3af0cdc68096e194495faba9d)) + +**Full Changelog:** [`2024.12.26.0...2024.12.26.1`](https://github.com/juspay/hyperswitch/compare/2024.12.26.0...2024.12.26.1) + +- - - + +## 2024.12.26.0 + +### Features + +- **router:** Add endpoint for listing connector features ([#6612](https://github.com/juspay/hyperswitch/pull/6612)) ([`a423ff5`](https://github.com/juspay/hyperswitch/commit/a423ff53d3523508ba6c584134e32f3f1bb4f0c0)) + +### Bug Fixes + +- **cors:** Expose all headers set by application in `access-control-expose-headers` header value ([#6877](https://github.com/juspay/hyperswitch/pull/6877)) ([`9c3547f`](https://github.com/juspay/hyperswitch/commit/9c3547fa8dd5930613380014025add33ccd5db4a)) +- **payments_list:** Handle same payment/attempt ids for different merchants ([#6917](https://github.com/juspay/hyperswitch/pull/6917)) ([`2e472e3`](https://github.com/juspay/hyperswitch/commit/2e472e3fee0c33b3ce8affc520db7a512ed41b2f)) +- **wasm:** Remove chasenet from jpmorgan wasm as ChaseNet doesn’t exist in PMT ([#6927](https://github.com/juspay/hyperswitch/pull/6927)) ([`5e4eded`](https://github.com/juspay/hyperswitch/commit/5e4eded8fa13c9cb4a1d648ab5c133e86522c29c)) + +### Refactors + +- **core:** Remove merchant return url from `router_data` ([#6895](https://github.com/juspay/hyperswitch/pull/6895)) ([`c5717a8`](https://github.com/juspay/hyperswitch/commit/c5717a8147899e0c690e234dbf9b4fd425a7bb71)) + +**Full Changelog:** [`2024.12.24.0...2024.12.26.0`](https://github.com/juspay/hyperswitch/compare/2024.12.24.0...2024.12.26.0) + +- - - + +## 2024.12.24.0 + +### Features + +- **core:** Implemented platform merchant account ([#6882](https://github.com/juspay/hyperswitch/pull/6882)) ([`95fcf2a`](https://github.com/juspay/hyperswitch/commit/95fcf2a44ba463f90a145baae8ab9d57cc12d8fa)) +- **cypress:** Valdiate `error_code` and `error_message` and make it visible in `reports` ([#6913](https://github.com/juspay/hyperswitch/pull/6913)) ([`46b2bfe`](https://github.com/juspay/hyperswitch/commit/46b2bfe48a9dfea8fc81c9cc95a98a9a331c04c8)) +- **payments_v2:** Add payment method list endpoint ([#6805](https://github.com/juspay/hyperswitch/pull/6805)) ([`d4b3dbc`](https://github.com/juspay/hyperswitch/commit/d4b3dbc155906e8bc0fa1b14e73f45227395a32f)) +- **router:** Add /retrieve api for relay ([#6918](https://github.com/juspay/hyperswitch/pull/6918)) ([`0478731`](https://github.com/juspay/hyperswitch/commit/04787313941ec39b179490d0196258f09e2e51dd)) + +### Bug Fixes + +- **connector:** [Cybersource] fix the required fields for wallet mandate payments ([#6911](https://github.com/juspay/hyperswitch/pull/6911)) ([`1fc9410`](https://github.com/juspay/hyperswitch/commit/1fc941056fb8759435f41bba004a602c176eb802)) +- **wasm:** + - Fix feature dependencies in `connector_configs` crate for WASM builds ([#6832](https://github.com/juspay/hyperswitch/pull/6832)) ([`6eabc82`](https://github.com/juspay/hyperswitch/commit/6eabc824d6ffb65562499943676820157efabb84)) + - Remove extra space from wasm for payment_method_type of JPMorgan ([#6923](https://github.com/juspay/hyperswitch/pull/6923)) ([`4465385`](https://github.com/juspay/hyperswitch/commit/44653850f0128314e2580c8001937ca4a45e4b02)) + +**Full Changelog:** [`2024.12.23.0...2024.12.24.0`](https://github.com/juspay/hyperswitch/compare/2024.12.23.0...2024.12.24.0) + +- - - + +## 2024.12.23.0 + +### Features + +- **connector:** [JPMORGAN] add Payment flows for cards ([#6668](https://github.com/juspay/hyperswitch/pull/6668)) ([`adcddd6`](https://github.com/juspay/hyperswitch/commit/adcddd643c002a5fe3e7c50c0f78fa5a46f210e7)) +- **payment_methods_v2:** Added Ephemeral auth for v2 ([#6813](https://github.com/juspay/hyperswitch/pull/6813)) ([`24401bc`](https://github.com/juspay/hyperswitch/commit/24401bc16f9677ce0f5fa70d739e5e6885c7e907)) +- **payments_v2:** Implement payments capture v2 ([#6722](https://github.com/juspay/hyperswitch/pull/6722)) ([`977cb70`](https://github.com/juspay/hyperswitch/commit/977cb704e7dcf35d0fa6bc0e3c6d335ad0601521)) +- **router:** + - Add /relay endpoint ([#6870](https://github.com/juspay/hyperswitch/pull/6870)) ([`22de8ad`](https://github.com/juspay/hyperswitch/commit/22de8ad132811b636fdb2594649e40b90810f564)) + - Add db interface for `/relay` ([#6879](https://github.com/juspay/hyperswitch/pull/6879)) ([`0f8b0b3`](https://github.com/juspay/hyperswitch/commit/0f8b0b3bc854be62942a77d08340510312157c67)) + +### Bug Fixes + +- **connector:** + - Paypal BankRedirects (Ideal/EPS) ([#6864](https://github.com/juspay/hyperswitch/pull/6864)) ([`dcd51a7`](https://github.com/juspay/hyperswitch/commit/dcd51a7fb8df673cc74130ee732542b55783602f)) + - [STRIPE] fix stripe mandate ([#6899](https://github.com/juspay/hyperswitch/pull/6899)) ([`9f2ce05`](https://github.com/juspay/hyperswitch/commit/9f2ce05b2591da0d757b267800f42b69fc38e3ee)) + - Update mandate PMT configs for Mandate Supported Connectors ([#6903](https://github.com/juspay/hyperswitch/pull/6903)) ([`02f0824`](https://github.com/juspay/hyperswitch/commit/02f0824d303fb9a36ee54123f52176014613a992)) +- Cypress reports generation ([#6894](https://github.com/juspay/hyperswitch/pull/6894)) ([`81b324c`](https://github.com/juspay/hyperswitch/commit/81b324caf1bef4f621de30824bfb1f05ef984362)) + +### Refactors + +- **connector:** [Airwallex] add device_data in payment request ([#6881](https://github.com/juspay/hyperswitch/pull/6881)) ([`573974b`](https://github.com/juspay/hyperswitch/commit/573974b3a5d53b279bd3959e400ac682aaacd474)) +- **customers_v2:** Include minor fixes for customer v2 flows ([#6876](https://github.com/juspay/hyperswitch/pull/6876)) ([`5cdeaf8`](https://github.com/juspay/hyperswitch/commit/5cdeaf8e6002ad087dba2a562f86b51e97516d29)) +- **dynamic_routing:** Add col payment_method_type in dynamic_routing_stats ([#6853](https://github.com/juspay/hyperswitch/pull/6853)) ([`492fd87`](https://github.com/juspay/hyperswitch/commit/492fd871a14e60e02f17fe073544bc40e79a7220)) +- **grpc:** Send `x-tenant-id` and `x-request-id` in grpc headers ([#6904](https://github.com/juspay/hyperswitch/pull/6904)) ([`dc0a92d`](https://github.com/juspay/hyperswitch/commit/dc0a92dc108c91d6c5f998af417e382aa7a0d9f1)) + +### Miscellaneous Tasks + +- **cypress:** Payout - fix test cases for adyenplatform bank ([#6887](https://github.com/juspay/hyperswitch/pull/6887)) ([`7540b74`](https://github.com/juspay/hyperswitch/commit/7540b7434766ff9dfa1aa2a56013ac89429dd1e6)) + +**Full Changelog:** [`2024.12.19.1...2024.12.23.0`](https://github.com/juspay/hyperswitch/compare/2024.12.19.1...2024.12.23.0) + +- - - + +## 2024.12.19.1 + +### Features + +- **core:** Added customer phone_number and email to session token response for click to pay ([#6863](https://github.com/juspay/hyperswitch/pull/6863)) ([`092c79e`](https://github.com/juspay/hyperswitch/commit/092c79ec40c6af47a5d6654129411300e42eac56)) +- **klarna:** Klarna Kustom Checkout Integration ([#6839](https://github.com/juspay/hyperswitch/pull/6839)) ([`c525c9f`](https://github.com/juspay/hyperswitch/commit/c525c9f4c9d23802989bc594a4acd26c7d7cd27d)) +- **payment_methods:** Add support to pass apple pay recurring details to obtain apple pay merchant token ([#6770](https://github.com/juspay/hyperswitch/pull/6770)) ([`6074249`](https://github.com/juspay/hyperswitch/commit/607424992af4196f5a3e01477f64d794b3594a47)) +- **payments:** [Payment links] Add config for changing button text for payment links ([#6860](https://github.com/juspay/hyperswitch/pull/6860)) ([`46aad50`](https://github.com/juspay/hyperswitch/commit/46aad503b04efe60c54bbf4d5d5122696d9b1157)) +- **users:** Handle email url for users in different tenancies ([#6809](https://github.com/juspay/hyperswitch/pull/6809)) ([`839e69d`](https://github.com/juspay/hyperswitch/commit/839e69df241cf0eb2495f0ad3fc19cf32632c741)) + +### Bug Fixes + +- **connector:** [UNIFIED_AUTHENTICATION_SERVICE] change url path to `pre_authentication_processing` in pre-auth flow ([#6885](https://github.com/juspay/hyperswitch/pull/6885)) ([`f219b74`](https://github.com/juspay/hyperswitch/commit/f219b74cb6a100e07084afe6d9242a88f7127971)) + +### Refactors + +- **users:** Move roles schema to global interface ([#6862](https://github.com/juspay/hyperswitch/pull/6862)) ([`2d8af88`](https://github.com/juspay/hyperswitch/commit/2d8af882046bbfe309c5dbb5be9bfbd43e0c3831)) + +**Full Changelog:** [`2024.12.19.0...2024.12.19.1`](https://github.com/juspay/hyperswitch/compare/2024.12.19.0...2024.12.19.1) + +- - - + +## 2024.12.19.0 + +### Refactors + +- **dynamic_routing:** Update the authentication for update config to include JWT type ([#6785](https://github.com/juspay/hyperswitch/pull/6785)) ([`db51ec4`](https://github.com/juspay/hyperswitch/commit/db51ec43bc629dc20ceaa2bb57ede888d2d2fc2c)) + +### Miscellaneous Tasks + +- **env:** Remove unified_authentication_service base_url from integ, sandbox and production toml ([#6865](https://github.com/juspay/hyperswitch/pull/6865)) ([`03c71ea`](https://github.com/juspay/hyperswitch/commit/03c71ea366041af060b385dc9d88d4b9eda4abea)) + +**Full Changelog:** [`2024.12.18.0...2024.12.19.0`](https://github.com/juspay/hyperswitch/compare/2024.12.18.0...2024.12.19.0) + +- - - + +## 2024.12.18.0 + +### Features + +- **analytics:** Analytics Request Validator and config driven forex feature ([#6733](https://github.com/juspay/hyperswitch/pull/6733)) ([`c883aa5`](https://github.com/juspay/hyperswitch/commit/c883aa59aae4ddbcf8c754052ed60b4514043d47)) +- **redis-interface:** Add redis interface command to set multiple the keys in redis and increment if the key already exists ([#6827](https://github.com/juspay/hyperswitch/pull/6827)) ([`94ad90f`](https://github.com/juspay/hyperswitch/commit/94ad90f9ed8b2d8a0e4715875f3fdccf2abec15d)) + +### Bug Fixes + +- **connector:** + - 5xx error for Volt Payment Sync ([#6846](https://github.com/juspay/hyperswitch/pull/6846)) ([`588ce40`](https://github.com/juspay/hyperswitch/commit/588ce408b4b04bdd89f2594239e7efc9e0f66114)) + - Add expiry year conversion for adyen mit transactions ([#6851](https://github.com/juspay/hyperswitch/pull/6851)) ([`c154a38`](https://github.com/juspay/hyperswitch/commit/c154a385597104fcdbed4aa859c52c97a240c39f)) +- **core:** Populate off_session based on payments request ([#6855](https://github.com/juspay/hyperswitch/pull/6855)) ([`107098c`](https://github.com/juspay/hyperswitch/commit/107098cda45440f9d80c6305b7b6e5cd3de9ca0d)) +- **payment_methods:** Card_network and card_scheme should be consistent ([#6849](https://github.com/juspay/hyperswitch/pull/6849)) ([`5c4de8a`](https://github.com/juspay/hyperswitch/commit/5c4de8a5133c9a835d8c706c9b71bdfc8140568d)) + +### Refactors + +- **constraint_graph:** Handle PML for cases where setup_future_usage is not passed in payments ([#6810](https://github.com/juspay/hyperswitch/pull/6810)) ([`e8bfd0e`](https://github.com/juspay/hyperswitch/commit/e8bfd0e2270300fff3f051143f34ebb782da5366)) +- **customers_v2:** Address panics and some bugs in customers v2 endpoints ([#6836](https://github.com/juspay/hyperswitch/pull/6836)) ([`dfbfce4`](https://github.com/juspay/hyperswitch/commit/dfbfce4e4247166e43f1a805e65331b21eab4e09)) + +### Miscellaneous Tasks + +- **analytics:** SDK table schema changes ([#6579](https://github.com/juspay/hyperswitch/pull/6579)) ([`a056dc7`](https://github.com/juspay/hyperswitch/commit/a056dc72db23200c473e8aa2ec8ce5579fa4f6c6)) +- **wasm:** Add wasm changes for ctp_mastercard connector ([#6838](https://github.com/juspay/hyperswitch/pull/6838)) ([`b301d09`](https://github.com/juspay/hyperswitch/commit/b301d09213a8c1c68d711a3b34227d13e61e52f9)) + +**Full Changelog:** [`2024.12.17.0...2024.12.18.0`](https://github.com/juspay/hyperswitch/compare/2024.12.17.0...2024.12.18.0) + +- - - + +## 2024.12.17.0 + +### Features + +- **connector:** + - [AIRWALLEX] Add refferer data to whitelist hyperswitch ([#6806](https://github.com/juspay/hyperswitch/pull/6806)) ([`ed276ec`](https://github.com/juspay/hyperswitch/commit/ed276ecc0017f7f98b6f8fa3841e6b8971f609f1)) + - [Adyen ] Add fixes for AdyenPaymentRequest struct ([#6803](https://github.com/juspay/hyperswitch/pull/6803)) ([`c22be0c`](https://github.com/juspay/hyperswitch/commit/c22be0c9274350a531cd74b64eb6b311579dca79)) +- **core:** Add click to pay support in hyperswitch ([#6769](https://github.com/juspay/hyperswitch/pull/6769)) ([`165ead6`](https://github.com/juspay/hyperswitch/commit/165ead61084a48f268829c281e932b278f0a6730)) +- **payments:** Add audit events for PaymentStatus update ([#6520](https://github.com/juspay/hyperswitch/pull/6520)) ([`ae00a10`](https://github.com/juspay/hyperswitch/commit/ae00a103de5bd283695969270a421c7609a699e8)) +- **users:** Incorporate themes in user APIs ([#6772](https://github.com/juspay/hyperswitch/pull/6772)) ([`4b989fe`](https://github.com/juspay/hyperswitch/commit/4b989fe0fb7931479e127fecbaace42d989c0620)) + +### Bug Fixes + +- **router:** + - Handle default case for card_network for co-badged cards ([#6825](https://github.com/juspay/hyperswitch/pull/6825)) ([`f95ee51`](https://github.com/juspay/hyperswitch/commit/f95ee51bb3b879762d493953b4b6e7c2e0359946)) + - Change click_to_pay const to snake_case and remove camel_case serde rename for clicktopay metadata ([#6852](https://github.com/juspay/hyperswitch/pull/6852)) ([`3d4fd2f`](https://github.com/juspay/hyperswitch/commit/3d4fd2f719b38dcbb675de83c0ba384d1573df00)) +- **user_roles:** Migrations for backfilling user_roles entity_id ([#6837](https://github.com/juspay/hyperswitch/pull/6837)) ([`986de77`](https://github.com/juspay/hyperswitch/commit/986de77b4868e48d00161c9d30071d809360e9a6)) + +### Refactors + +- **authz:** Make connector list accessible by operation groups ([#6792](https://github.com/juspay/hyperswitch/pull/6792)) ([`6081283`](https://github.com/juspay/hyperswitch/commit/6081283afc5ab5a6503c8f0f81181cd323b12297)) + +### Miscellaneous Tasks + +- **deps:** Update scylla driver ([#6799](https://github.com/juspay/hyperswitch/pull/6799)) ([`71574a8`](https://github.com/juspay/hyperswitch/commit/71574a85e6aba6bc614e1d7f6775dcef4b481201)) + +**Full Changelog:** [`2024.12.16.0...2024.12.17.0`](https://github.com/juspay/hyperswitch/compare/2024.12.16.0...2024.12.17.0) + +- - - + +## 2024.12.16.0 + +### Features + +- **router:** Add `click_to_pay` block in payments sessions response if enabled ([#6829](https://github.com/juspay/hyperswitch/pull/6829)) ([`5aa8ea0`](https://github.com/juspay/hyperswitch/commit/5aa8ea03a8327b4eb12646f1bfe5522c6dfc0282)) +- **routing:** Build the gRPC interface for communicating with the external service to perform elimination routing ([#6672](https://github.com/juspay/hyperswitch/pull/6672)) ([`2a66f4a`](https://github.com/juspay/hyperswitch/commit/2a66f4a392a5175404816ba83736e3eeb3e2b53b)) + +### Bug Fixes + +- **webhooks:** Mask custom outgoing webhook headers in profile response ([#6798](https://github.com/juspay/hyperswitch/pull/6798)) ([`09cf7a3`](https://github.com/juspay/hyperswitch/commit/09cf7a3ea9db3f760eb1c35ef3074dfedc8fc33f)) + +### Refactors + +- **core:** Structure of split payments ([#6706](https://github.com/juspay/hyperswitch/pull/6706)) ([`5a85213`](https://github.com/juspay/hyperswitch/commit/5a85213e21702992bff1fc0b0345be2ea4f30981)) + +**Full Changelog:** [`2024.12.13.0...2024.12.16.0`](https://github.com/juspay/hyperswitch/compare/2024.12.13.0...2024.12.16.0) + +- - - + +## 2024.12.13.0 + +### Features + +- **connector:** [DEUTSCHEBANK, FIUU ] Handle 2xx errors given by Connector ([#6727](https://github.com/juspay/hyperswitch/pull/6727)) ([`573fc2c`](https://github.com/juspay/hyperswitch/commit/573fc2ce0ff306d15ec97e7c8d5b8a03528165f4)) +- **core:** + - Add service details field in authentication table ([#6757](https://github.com/juspay/hyperswitch/pull/6757)) ([`e9a5615`](https://github.com/juspay/hyperswitch/commit/e9a5615f2ba1f6cc27bbef653c42326b50da8db7)) + - Add product authentication ids in business profile ([#6811](https://github.com/juspay/hyperswitch/pull/6811)) ([`1564ad7`](https://github.com/juspay/hyperswitch/commit/1564ad72b80b184808584f97309620a18246d80c)) + - Payment links - add support for custom background image and layout in details section ([#6725](https://github.com/juspay/hyperswitch/pull/6725)) ([`d11d874`](https://github.com/juspay/hyperswitch/commit/d11d87408d0c4195bbe2c4c51df50f24c1d332c6)) + +### Refactors + +- **connector:** Move connectors Datatrans, Paybox, Placetopay, Bluesnap from router crate to hyperswitch_connector crate ([#6730](https://github.com/juspay/hyperswitch/pull/6730)) ([`da5c34a`](https://github.com/juspay/hyperswitch/commit/da5c34a335043cb225ed0e4ee06cd75a83c92c4d)) +- **kafka_message:** NanoSecond precision for consolidated logs ([#6771](https://github.com/juspay/hyperswitch/pull/6771)) ([`fb3a49b`](https://github.com/juspay/hyperswitch/commit/fb3a49be658c3c4374ca98f9eae5d88dc92a3669)) + +**Full Changelog:** [`2024.12.12.0...2024.12.13.0`](https://github.com/juspay/hyperswitch/compare/2024.12.12.0...2024.12.13.0) + +- - - + +## 2024.12.12.0 + +### Features + +- **core:** Add uas framework support ([#6743](https://github.com/juspay/hyperswitch/pull/6743)) ([`9466ced`](https://github.com/juspay/hyperswitch/commit/9466ced89407f31963bb0eb7c762749e3713591a)) + +### Bug Fixes + +- **router:** Card network for co-badged card and update regex ([#6801](https://github.com/juspay/hyperswitch/pull/6801)) ([`cd20537`](https://github.com/juspay/hyperswitch/commit/cd205378c035780586f6b94e5c9e03466165a33b)) + +**Full Changelog:** [`2024.12.11.0...2024.12.12.0`](https://github.com/juspay/hyperswitch/compare/2024.12.11.0...2024.12.12.0) + +- - - + +## 2024.12.11.0 + +### Features + +- **analytics:** Add support for multiple emails as input to forward reports ([#6776](https://github.com/juspay/hyperswitch/pull/6776)) ([`3df4233`](https://github.com/juspay/hyperswitch/commit/3df42333566b646e9ca93d612a78ea8d38298df4)) +- **connector:** [Unifiedauthenticationservice] add Connector Template Code ([#6732](https://github.com/juspay/hyperswitch/pull/6732)) ([`8777f41`](https://github.com/juspay/hyperswitch/commit/8777f41568ebf5373917089d7d42f3b14fb1bf60)) +- **payments:** [Payment links] Add locale case fix ([#6789](https://github.com/juspay/hyperswitch/pull/6789)) ([`8431842`](https://github.com/juspay/hyperswitch/commit/84318427108a0f974b2519587d0e336807a9600c)) + +### Bug Fixes + +- **core:** + - Add validation to check if routable connector supports network tokenization in CIT repeat flow ([#6749](https://github.com/juspay/hyperswitch/pull/6749)) ([`9f0d8ef`](https://github.com/juspay/hyperswitch/commit/9f0d8efa8dad45a773f4cab6978288f2209e4abf)) + - Payments - map billing first and last name to card holder name ([#6791](https://github.com/juspay/hyperswitch/pull/6791)) ([`c3b22cf`](https://github.com/juspay/hyperswitch/commit/c3b22cf81a5c8cbc6538ca7f7e4b1ce4d18eb644)) +- **docs:** Incorrect description for refund api ([#6443](https://github.com/juspay/hyperswitch/pull/6443)) ([`8954e8a`](https://github.com/juspay/hyperswitch/commit/8954e8a2180d20719b1bb0d4f77081ff03fd9b43)) + +### Refactors + +- **constraint_graph:** Add setup_future_usage for mandate check in payments ([#6744](https://github.com/juspay/hyperswitch/pull/6744)) ([`1aa4ad6`](https://github.com/juspay/hyperswitch/commit/1aa4ad60e2326cbdc5c81479cf3420c3f3e1d8ee)) +- **enums:** Recon - include ReconOps variant in PermissionsGroup for backwards compatibility with data in DB ([#6767](https://github.com/juspay/hyperswitch/pull/6767)) ([`a528282`](https://github.com/juspay/hyperswitch/commit/a52828296a682e30badf0849921469cdf4eecbea)) +- **events:** Tenant config in API, Connector and Outgoing Web-hook events ([#6777](https://github.com/juspay/hyperswitch/pull/6777)) ([`c620779`](https://github.com/juspay/hyperswitch/commit/c620779bbd14a1102d4fff68cc36581935d87da7)) +- **payment_methods:** Add new field_type UserBsbNumber, UserBankSortCode and UserBankRoutingNumber for payment_connector_required_fields ([#6758](https://github.com/juspay/hyperswitch/pull/6758)) ([`6f84145`](https://github.com/juspay/hyperswitch/commit/6f841458f73cec8ce43a34b1b50abbc74baa2ef7)) +- **users:** Remove lineage checks in roles get operations ([#6701](https://github.com/juspay/hyperswitch/pull/6701)) ([`f96a87d`](https://github.com/juspay/hyperswitch/commit/f96a87d08ca003411d63dcd9ef4dda6439d20e07)) + +### Documentation + +- Add new logos for README and API reference ([#6783](https://github.com/juspay/hyperswitch/pull/6783)) ([`b9c04c3`](https://github.com/juspay/hyperswitch/commit/b9c04c39880aa1ab0b66397802d138f0d4c1ed28)) + +### Build System / Dependencies + +- **deps:** Bump opentelemetry crates to 0.27 ([#6774](https://github.com/juspay/hyperswitch/pull/6774)) ([`47a3d2b`](https://github.com/juspay/hyperswitch/commit/47a3d2b2abcc28a13f79bd9318d119f103b7fb6c)) + +**Full Changelog:** [`2024.12.10.0...2024.12.11.0`](https://github.com/juspay/hyperswitch/compare/2024.12.10.0...2024.12.11.0) + +- - - + +## 2024.12.10.0 + +### Features + +- **core:** Add payments update-intent API for v2 ([#6490](https://github.com/juspay/hyperswitch/pull/6490)) ([`19f810a`](https://github.com/juspay/hyperswitch/commit/19f810aed8723456bdd20587f4c0ca6092d4677b)) + +**Full Changelog:** [`2024.12.09.0...2024.12.10.0`](https://github.com/juspay/hyperswitch/compare/2024.12.09.0...2024.12.10.0) + +- - - + +## 2024.12.09.0 + +### Features + +- **dynamic_routing:** Analytics improvement using separate postgres table ([#6723](https://github.com/juspay/hyperswitch/pull/6723)) ([`5918014`](https://github.com/juspay/hyperswitch/commit/5918014da158abbf44540c855e35b0b5bb363fb2)) +- **users:** Add support for tenant level users ([#6708](https://github.com/juspay/hyperswitch/pull/6708)) ([`357e8a0`](https://github.com/juspay/hyperswitch/commit/357e8a007ac5d418c143e90b829d938e7cbcb69e)) + +### Bug Fixes + +- **connector:** Add config cleanup on payment connector deletion ([#5998](https://github.com/juspay/hyperswitch/pull/5998)) ([`512ae85`](https://github.com/juspay/hyperswitch/commit/512ae85c81fc92158e1b54c48b55993849e14a2a)) +- **core:** Card_network details Missing in Customer Payment Methods List for External 3DS Authentication Payments ([#6739](https://github.com/juspay/hyperswitch/pull/6739)) ([`15f873b`](https://github.com/juspay/hyperswitch/commit/15f873bd1296169149987041f4008b0afe2ac2aa)) +- **router:** Validate each field for migration request body ([#6525](https://github.com/juspay/hyperswitch/pull/6525)) ([`b5d3d49`](https://github.com/juspay/hyperswitch/commit/b5d3d49ceaa2f89284ae5976afec0ff5663a24b0)) + +**Full Changelog:** [`2024.12.06.0...2024.12.09.0`](https://github.com/juspay/hyperswitch/compare/2024.12.06.0...2024.12.09.0) + +- - - + +## 2024.12.06.0 + +### Features + +- **analytics:** Add refund sessionized metrics for Analytics V2 dashboard ([#6616](https://github.com/juspay/hyperswitch/pull/6616)) ([`774a53e`](https://github.com/juspay/hyperswitch/commit/774a53ee8935e2e28827b986e5bf0ed5dc55cf33)) +- **connector:** + - [Nexixpay] add mandates flow for cards ([#6259](https://github.com/juspay/hyperswitch/pull/6259)) ([`62521f3`](https://github.com/juspay/hyperswitch/commit/62521f367bbbf1f9153b506934eafee7eb58e2fb)) + - Added a new CaptureMethod SequentialAutomatic to Support CIT Mandates for Paybox ([#6587](https://github.com/juspay/hyperswitch/pull/6587)) ([`e5dde6a`](https://github.com/juspay/hyperswitch/commit/e5dde6acc0c83b97590a23e1a4aa98f2db4fe954)) +- **core:** Add is_click_to_pay_enabled in business profile ([#6736](https://github.com/juspay/hyperswitch/pull/6736)) ([`4bfabdf`](https://github.com/juspay/hyperswitch/commit/4bfabdfa24b24c4bc2dddfca4bd8dd7b34003863)) +- **events:** Add audit event for CompleteAuthorize ([#6310](https://github.com/juspay/hyperswitch/pull/6310)) ([`dc26317`](https://github.com/juspay/hyperswitch/commit/dc26317e9bc1aa82666e978c5e824ccb9b016d31)) +- **payments:** [Payment links] Add support for traditional chinese locale for payment links ([#6745](https://github.com/juspay/hyperswitch/pull/6745)) ([`5704ca1`](https://github.com/juspay/hyperswitch/commit/5704ca12616b441e3fc9bba19f9398e05e1fac96)) +- **routing:** Enable volume split for dynamic routing ([#6662](https://github.com/juspay/hyperswitch/pull/6662)) ([`03b936a`](https://github.com/juspay/hyperswitch/commit/03b936a117ae0931fab800cb82038ba45aa6f9a3)) +- **webhooks:** Adyen - consume and update connector's network_transaction_id in payment_methods ([#6738](https://github.com/juspay/hyperswitch/pull/6738)) ([`871a363`](https://github.com/juspay/hyperswitch/commit/871a36379d5b40a6ce98232275a7cc8982c32ea8)) + +### Bug Fixes + +- **api_models:** Fix `wasm` build problems caused by `actix-multipart` ([#6747](https://github.com/juspay/hyperswitch/pull/6747)) ([`437a8de`](https://github.com/juspay/hyperswitch/commit/437a8de8ebd8af97a7df51dd81174cf36ca44e5f)) + +### Refactors + +- **connector:** Move connectors Bamboraapac, Boku, Gocardless, Prophetpay, Rapyd ([#6652](https://github.com/juspay/hyperswitch/pull/6652)) ([`36388d4`](https://github.com/juspay/hyperswitch/commit/36388d458e799fc6cc58c1a405e46ea6a8ebd96c)) +- **connector-configs:** Worldpay - update username / password mapping ([#6752](https://github.com/juspay/hyperswitch/pull/6752)) ([`19f8ee4`](https://github.com/juspay/hyperswitch/commit/19f8ee46e5a075fecfa1f80d71960928821bf468)) +- **dynamic_fields:** Rename fields like ach, bacs and becs for bank debit payment method ([#6678](https://github.com/juspay/hyperswitch/pull/6678)) ([`c2646d7`](https://github.com/juspay/hyperswitch/commit/c2646d749c1eee916629ba80d930adeb1860fc4e)) + +### Documentation + +- Updating logo for Api ref ([#6741](https://github.com/juspay/hyperswitch/pull/6741)) ([`de80121`](https://github.com/juspay/hyperswitch/commit/de8012187180c35a61fbe990094b0c2d74b206c9)) + +### Miscellaneous Tasks + +- Enable `clippy::trivially_copy_pass_by_ref` lint and address it ([#6724](https://github.com/juspay/hyperswitch/pull/6724)) ([`d17d2fe`](https://github.com/juspay/hyperswitch/commit/d17d2fe075bee35c3449bfb7db356df83f49a045)) + +**Full Changelog:** [`2024.12.05.0...2024.12.06.0`](https://github.com/juspay/hyperswitch/compare/2024.12.05.0...2024.12.06.0) + +- - - + +## 2024.12.05.0 + +### Features + +- **themes:** Create APIs for managing themes ([#6658](https://github.com/juspay/hyperswitch/pull/6658)) ([`3a3e93c`](https://github.com/juspay/hyperswitch/commit/3a3e93cb3be3fc3ffabef2a708b49defabf338a5)) +- Add resources and granular permission groups for reconciliation ([#6591](https://github.com/juspay/hyperswitch/pull/6591)) ([`fa21ef8`](https://github.com/juspay/hyperswitch/commit/fa21ef892da1b2ff511a39134ffdcc5d404dc91a)) + +### Refactors + +- **address:** Change address to domain address in application ([#6608](https://github.com/juspay/hyperswitch/pull/6608)) ([`938b2a8`](https://github.com/juspay/hyperswitch/commit/938b2a898ea3f647d57812858c6bd4dad13972a3)) +- **connector:** Add amount conversion framework to cybersource ([#6335](https://github.com/juspay/hyperswitch/pull/6335)) ([`248be9c`](https://github.com/juspay/hyperswitch/commit/248be9c73e7d627c856e5398234ff5840c93798c)) +- **gsm:** Add `error_category` column to gsm table ([#6648](https://github.com/juspay/hyperswitch/pull/6648)) ([`fd82cf6`](https://github.com/juspay/hyperswitch/commit/fd82cf610a15143559f8db1038c8c65ede6e7b7c)) + +### Miscellaneous Tasks + +- Wasm paze additional details ([#6710](https://github.com/juspay/hyperswitch/pull/6710)) ([`35f963c`](https://github.com/juspay/hyperswitch/commit/35f963c2e8a48add26bc80e6a828e2d18e6f1058)) + +**Full Changelog:** [`2024.12.04.0...2024.12.05.0`](https://github.com/juspay/hyperswitch/compare/2024.12.04.0...2024.12.05.0) + +- - - + +## 2024.12.04.0 + +### Features + +- **cypress:** Add multiple creds and flags support ([#6588](https://github.com/juspay/hyperswitch/pull/6588)) ([`6438391`](https://github.com/juspay/hyperswitch/commit/64383915bda5693df1cecf6cc5683e8b9aaef99b)) + +**Full Changelog:** [`2024.12.03.0...2024.12.04.0`](https://github.com/juspay/hyperswitch/compare/2024.12.03.0...2024.12.04.0) + +- - - + +## 2024.12.03.0 + +### Features + +- **payment_methods_v2:** Implement a barebones version of list customer payment methods v2 ([#6649](https://github.com/juspay/hyperswitch/pull/6649)) ([`797a0db`](https://github.com/juspay/hyperswitch/commit/797a0db7733c5b387564fb1bbc106d054c8dffa6)) +- **routing:** Elimination routing switch for toggling the feature ([#6568](https://github.com/juspay/hyperswitch/pull/6568)) ([`f6dde13`](https://github.com/juspay/hyperswitch/commit/f6dde13d6c2920761f236969a3862fe61f3e0e3d)) + +### Bug Fixes + +- **connector:** Adyen - propagate connector mandate details in incoming webhooks ([#6720](https://github.com/juspay/hyperswitch/pull/6720)) ([`bea4b9e`](https://github.com/juspay/hyperswitch/commit/bea4b9e7f430c3d7fbb25be0b472d2afb01135ec)) +- **opensearch:** Fix empty filter array query addition in globalsearch query ([#6716](https://github.com/juspay/hyperswitch/pull/6716)) ([`063a1c6`](https://github.com/juspay/hyperswitch/commit/063a1c636ce29ca8f76c3c272c6da4d32d356cda)) +- **payment_link:** Add support for hide card nickname field for open payment links ([#6700](https://github.com/juspay/hyperswitch/pull/6700)) ([`933911e`](https://github.com/juspay/hyperswitch/commit/933911eda11f32d72ffeddb948b86672cb08105b)) + +### Miscellaneous Tasks + +- Address Rust 1.83.0 clippy lints and enable more clippy lints ([#6705](https://github.com/juspay/hyperswitch/pull/6705)) ([`9a59d0a`](https://github.com/juspay/hyperswitch/commit/9a59d0a5ff682cd7a983a63e90113afc846aeac6)) + +**Full Changelog:** [`2024.12.02.1...2024.12.03.0`](https://github.com/juspay/hyperswitch/compare/2024.12.02.1...2024.12.03.0) + +- - - + +## 2024.12.02.1 + +### Bug Fixes + +- **openapi:** Revert Standardise API naming scheme for V2 Dashboard Changes ([#6712](https://github.com/juspay/hyperswitch/pull/6712)) ([`b097d7f`](https://github.com/juspay/hyperswitch/commit/b097d7f5a984b32421494ea033029d01d034fab8)) + +**Full Changelog:** [`2024.12.02.0...2024.12.02.1`](https://github.com/juspay/hyperswitch/compare/2024.12.02.0...2024.12.02.1) + +- - - + +## 2024.12.02.0 + +### Features + +- **connector:** + - [Adyen] Fetch email from customer email for payment request ([#6676](https://github.com/juspay/hyperswitch/pull/6676)) ([`9998c55`](https://github.com/juspay/hyperswitch/commit/9998c557c9c88496ffbee883e7fc4b76614cff50)) + - [REDSYS] add Connector Template Code ([#6659](https://github.com/juspay/hyperswitch/pull/6659)) ([`19cbcdd`](https://github.com/juspay/hyperswitch/commit/19cbcdd979bb74119d80c37c313fd0ffeb58bb8d)) +- **payments:** [Payment links] add showCardFormByDefault config for payment links ([#6663](https://github.com/juspay/hyperswitch/pull/6663)) ([`b1d1073`](https://github.com/juspay/hyperswitch/commit/b1d1073389f58c480a53a27be24aa91554520ff1)) +- **users:** Add tenant id reads in user roles ([#6661](https://github.com/juspay/hyperswitch/pull/6661)) ([`9212f77`](https://github.com/juspay/hyperswitch/commit/9212f77684b04115332d9be5c3d20bdc56b02160)) + +### Bug Fixes + +- **analytics:** Fix first_attempt filter value parsing for Payments ([#6667](https://github.com/juspay/hyperswitch/pull/6667)) ([`abcaa53`](https://github.com/juspay/hyperswitch/commit/abcaa539eccdae86c7a68fd4ce60ab9889f9fb43)) +- **openapi:** Standardise API naming scheme for V2 ([#6510](https://github.com/juspay/hyperswitch/pull/6510)) ([`96393ff`](https://github.com/juspay/hyperswitch/commit/96393ff3d6b11d4726a6cb2224236414507d9848)) +- **opensearch:** Handle empty free-text query search in global search ([#6685](https://github.com/juspay/hyperswitch/pull/6685)) ([`b1cdff0`](https://github.com/juspay/hyperswitch/commit/b1cdff0950f32b38e3ff0eeac2b726ba0f671051)) +- **router:** Populate card network in the network transaction id based MIT flow ([#6690](https://github.com/juspay/hyperswitch/pull/6690)) ([`6a20701`](https://github.com/juspay/hyperswitch/commit/6a2070172b8d845e6db36b7789defddf8ea4e1e9)) +- **users:** Mark user as verified if user logins from SSO ([#6694](https://github.com/juspay/hyperswitch/pull/6694)) ([`880ad1e`](https://github.com/juspay/hyperswitch/commit/880ad1e883fb42f73c2805287e64bc2c2dcbb9f3)) + +### Refactors + +- **currency_conversion:** Release redis lock if api call fails ([#6671](https://github.com/juspay/hyperswitch/pull/6671)) ([`ae7d16e`](https://github.com/juspay/hyperswitch/commit/ae7d16e23699c8ed95a7e2eab7539cfe20f847d0)) +- **router:** [ZSL] remove partially capture status ([#6689](https://github.com/juspay/hyperswitch/pull/6689)) ([`0572626`](https://github.com/juspay/hyperswitch/commit/05726262e6a3f6fcb18c0dbe41c18e4d6e84608b)) +- **users:** Use domain email type in user DB functions ([#6699](https://github.com/juspay/hyperswitch/pull/6699)) ([`55fe82f`](https://github.com/juspay/hyperswitch/commit/55fe82fdcd78df9608842190f1423088740d1087)) + +**Full Changelog:** [`2024.11.29.0...2024.12.02.0`](https://github.com/juspay/hyperswitch/compare/2024.11.29.0...2024.12.02.0) + +- - - + +## 2024.11.29.0 + +### Features + +- **connector:** Worldpay - add dynamic fields and update terminal status mapping ([#6468](https://github.com/juspay/hyperswitch/pull/6468)) ([`5a98ed6`](https://github.com/juspay/hyperswitch/commit/5a98ed65a94a6e8204a3ea34f834033654fdbaa7)) +- Add support for sdk session call in v2 ([#6502](https://github.com/juspay/hyperswitch/pull/6502)) ([`707f48c`](https://github.com/juspay/hyperswitch/commit/707f48ceda789185187d23e35f483e117c67b81b)) + +### Bug Fixes + +- **analytics:** Fix bugs in payments page metrics in Analytics V2 dashboard ([#6654](https://github.com/juspay/hyperswitch/pull/6654)) ([`93459fd`](https://github.com/juspay/hyperswitch/commit/93459fde5fb95f31e8f1429e806cde8e7496dd84)) + +**Full Changelog:** [`2024.11.28.0...2024.11.29.0`](https://github.com/juspay/hyperswitch/compare/2024.11.28.0...2024.11.29.0) + +- - - + +## 2024.11.28.0 + +### Bug Fixes + +- **users:** Check lineage across entities in invite ([#6677](https://github.com/juspay/hyperswitch/pull/6677)) ([`f3424b7`](https://github.com/juspay/hyperswitch/commit/f3424b7576554215945f61b52f38e43bb1e5a8b7)) + +### Refactors + +- **core:** Add error handling wrapper to wehbook ([#6636](https://github.com/juspay/hyperswitch/pull/6636)) ([`4b45d21`](https://github.com/juspay/hyperswitch/commit/4b45d21269437479435302aa1ea7d3d741e2a009)) + +**Full Changelog:** [`2024.11.27.0...2024.11.28.0`](https://github.com/juspay/hyperswitch/compare/2024.11.27.0...2024.11.28.0) + +- - - + +## 2024.11.27.0 + +### Features + +- **analytics:** Add `sessionized_metrics` for disputes analytics ([#6573](https://github.com/juspay/hyperswitch/pull/6573)) ([`8fbb766`](https://github.com/juspay/hyperswitch/commit/8fbb7663089d4790628109944e5fb5a57ccdaf00)) +- **connector:** + - [INESPAY] add Connector Template Code ([#6614](https://github.com/juspay/hyperswitch/pull/6614)) ([`710186f`](https://github.com/juspay/hyperswitch/commit/710186f035c92a919e8f5a49565c6f8908f1803f)) + - [Netcetera] add sca exemption ([#6611](https://github.com/juspay/hyperswitch/pull/6611)) ([`3120494`](https://github.com/juspay/hyperswitch/commit/31204941ee24fe7b23344ba9b4a2615c46f33bb0)) +- **payments:** Propagate additional payment method data for google pay during MIT ([#6644](https://github.com/juspay/hyperswitch/pull/6644)) ([`75fe9c0`](https://github.com/juspay/hyperswitch/commit/75fe9c0c285f640967af33b1d969af9ce48c5b17)) +- **router:** [Cybersource] add PLN to the currency config ([#6628](https://github.com/juspay/hyperswitch/pull/6628)) ([`29a0885`](https://github.com/juspay/hyperswitch/commit/29a0885a8fc7b718f8b87866e2638e8bfad3c8f3)) +- **users:** Send welcome to community email in magic link signup ([#6639](https://github.com/juspay/hyperswitch/pull/6639)) ([`03423a1`](https://github.com/juspay/hyperswitch/commit/03423a1f76d324453052da985f998fd3f957ce90)) +- Added grpc based health check ([#6441](https://github.com/juspay/hyperswitch/pull/6441)) ([`e922f96`](https://github.com/juspay/hyperswitch/commit/e922f96cee7e34493f0022b0c56455357eddc4f8)) + +### Bug Fixes + +- **core:** Add payment_id as query param in merchant return url ([#6665](https://github.com/juspay/hyperswitch/pull/6665)) ([`6829478`](https://github.com/juspay/hyperswitch/commit/682947866e6afc197c71bbd255f22ae427704590)) + +### Refactors + +- **authn:** Enable cookies in Integ ([#6599](https://github.com/juspay/hyperswitch/pull/6599)) ([`02479a1`](https://github.com/juspay/hyperswitch/commit/02479a12b18dc68e2787ae237580fcb46348374e)) +- **connector:** Add amount conversion framework to Riskified ([#6359](https://github.com/juspay/hyperswitch/pull/6359)) ([`acb30ef`](https://github.com/juspay/hyperswitch/commit/acb30ef6d144eaf13b237b830d1ac534259932a3)) +- **payments_v2:** Use batch encryption for intent create and confirm intent ([#6589](https://github.com/juspay/hyperswitch/pull/6589)) ([`108b160`](https://github.com/juspay/hyperswitch/commit/108b1603fa44b2a56c278196edb5a1f76f5d3d03)) +- **tenant:** Use tenant id type ([#6643](https://github.com/juspay/hyperswitch/pull/6643)) ([`c9df7b0`](https://github.com/juspay/hyperswitch/commit/c9df7b0557889c88ea20392dfe56bf651e22c9a7)) + +**Full Changelog:** [`2024.11.26.0...2024.11.27.0`](https://github.com/juspay/hyperswitch/compare/2024.11.26.0...2024.11.27.0) + +- - - + +## 2024.11.26.0 + +### Features + +- **connector:** + - [Paypal] implement vaulting for paypal cards via zero mandates ([#5324](https://github.com/juspay/hyperswitch/pull/5324)) ([`83e8bc0`](https://github.com/juspay/hyperswitch/commit/83e8bc0775c20e9d055e65bd13a2e8b1148092e1)) + - [Elavon] Implement cards Flow ([#6485](https://github.com/juspay/hyperswitch/pull/6485)) ([`6887681`](https://github.com/juspay/hyperswitch/commit/68876811a8817cdec09be407fbbbbf7f19992565)) +- **core:** Add SCA exemption field ([#6578](https://github.com/juspay/hyperswitch/pull/6578)) ([`2b8eb09`](https://github.com/juspay/hyperswitch/commit/2b8eb09a16040957ac369c48e6095c343207f0d3)) +- **payments:** Add merchant order ref id filter ([#6630](https://github.com/juspay/hyperswitch/pull/6630)) ([`57e64c2`](https://github.com/juspay/hyperswitch/commit/57e64c26ca4251b493c87bfe93799faaab4ffa89)) + +### Miscellaneous Tasks + +- **deps:** Update cypress packages to address CVE ([#6624](https://github.com/juspay/hyperswitch/pull/6624)) ([`0db3aed`](https://github.com/juspay/hyperswitch/commit/0db3aed1533856b9892369d7bb2430d90d091756)) + +**Full Changelog:** [`2024.11.25.0...2024.11.26.0`](https://github.com/juspay/hyperswitch/compare/2024.11.25.0...2024.11.26.0) + +- - - + +## 2024.11.25.0 + +### Features + +- **analytics:** Add `first_attempt` as a filter for PaymentFilters ([#6604](https://github.com/juspay/hyperswitch/pull/6604)) ([`9460041`](https://github.com/juspay/hyperswitch/commit/9460041b2ae8f94f2894517d3c04d30c6f78a5bb)) +- **refunds:** Trigger refund outgoing webhooks in create and retrieve refund flows ([#6635](https://github.com/juspay/hyperswitch/pull/6635)) ([`420eaab`](https://github.com/juspay/hyperswitch/commit/420eaabf3308b2fd2119183b0a2b462aa69b77b2)) + +### Bug Fixes + +- **analytics:** Remove first_attempt group by in Payment Intent old metrics ([#6627](https://github.com/juspay/hyperswitch/pull/6627)) ([`54e393b`](https://github.com/juspay/hyperswitch/commit/54e393bf9a55bdc4527a723b7a03968f21848a5e)) +- **connector:** [Cybersource] change commerce indicator for applepay ([#6634](https://github.com/juspay/hyperswitch/pull/6634)) ([`8d0639e`](https://github.com/juspay/hyperswitch/commit/8d0639ea6f22227253a44e6bd8272d9e55d17f92)) + +**Full Changelog:** [`2024.11.22.0...2024.11.25.0`](https://github.com/juspay/hyperswitch/compare/2024.11.22.0...2024.11.25.0) + +- - - + +## 2024.11.22.0 + +### Features + +- **connector:** + - [Xendit] Template PR ([#6593](https://github.com/juspay/hyperswitch/pull/6593)) ([`9bc363f`](https://github.com/juspay/hyperswitch/commit/9bc363f140afcdc3d4dc624d6410a42c33afaeed)) + - [AIRWALLEX] Update production endpoint ([#6632](https://github.com/juspay/hyperswitch/pull/6632)) ([`bc65a84`](https://github.com/juspay/hyperswitch/commit/bc65a848a14c1e5c8a50cf4bf5764a7af2918ac9)) +- **themes:** Add `theme_name` and `entity_type` in themes table ([#6621](https://github.com/juspay/hyperswitch/pull/6621)) ([`bf13c16`](https://github.com/juspay/hyperswitch/commit/bf13c16109d0113f900c806b0722895a36ec2d5a)) + +### Bug Fixes + +- **connector:** [Novalnet] Get email from customer email if billing.email is not present ([#6619](https://github.com/juspay/hyperswitch/pull/6619)) ([`9010214`](https://github.com/juspay/hyperswitch/commit/9010214c6e62a65f91e0eeca6d5f21468e5c63aa)) + +### Refactors + +- Update API response for JSON deserialization errors ([#6610](https://github.com/juspay/hyperswitch/pull/6610)) ([`40d3c38`](https://github.com/juspay/hyperswitch/commit/40d3c38b830a7163331778064d0e1917d30fc17e)) + +**Full Changelog:** [`2024.11.21.0...2024.11.22.0`](https://github.com/juspay/hyperswitch/compare/2024.11.21.0...2024.11.22.0) + +- - - + +## 2024.11.21.0 + +### Features + +- **email:** Add SMTP support to allow mails through self hosted/custom SMTP server ([#6617](https://github.com/juspay/hyperswitch/pull/6617)) ([`0f563b0`](https://github.com/juspay/hyperswitch/commit/0f563b069994f47bba1ba77c79fef6307f3760e8)) +- **router:** Add support for network token migration ([#6300](https://github.com/juspay/hyperswitch/pull/6300)) ([`012e352`](https://github.com/juspay/hyperswitch/commit/012e352db0477f5ddb4429cb0e4f5d781fd901a7)) +- **users:** Convert emails to lowercase from requests ([#6601](https://github.com/juspay/hyperswitch/pull/6601)) ([`c04f81e`](https://github.com/juspay/hyperswitch/commit/c04f81e3c4362369a92b2ead5ee1b28b4ca44b52)) + +### Bug Fixes + +- **connector:** [Volt] handle 5xx error for Volt payments webhooks ([#6576](https://github.com/juspay/hyperswitch/pull/6576)) ([`75ec96b`](https://github.com/juspay/hyperswitch/commit/75ec96b6131d470b39171415058106b3464de75a)) +- **dispute:** Change dispute currency type to currency enum ([#6454](https://github.com/juspay/hyperswitch/pull/6454)) ([`98aa84b`](https://github.com/juspay/hyperswitch/commit/98aa84b7e842ac85ce2461f3eab826a6c3783832)) + +### Refactors + +- **router:** Remove metadata, additional_merchant_data and connector_wallets_details from connector list api ([#6583](https://github.com/juspay/hyperswitch/pull/6583)) ([`5611769`](https://github.com/juspay/hyperswitch/commit/5611769964e372eb4690ef95ce950a2842f074d3)) + +**Full Changelog:** [`2024.11.20.0...2024.11.21.0`](https://github.com/juspay/hyperswitch/compare/2024.11.20.0...2024.11.21.0) + +- - - + +## 2024.11.20.0 + +### Features + +- **analytics:** Add `smart_retries` only metrics for analytics v2 dashboard ([#6575](https://github.com/juspay/hyperswitch/pull/6575)) ([`f3897dd`](https://github.com/juspay/hyperswitch/commit/f3897dd6b57318b681a2c5dc099d787aa8233f24)) +- **connector:** [Novalnet] Add minimal customer data feature ([#6570](https://github.com/juspay/hyperswitch/pull/6570)) ([`9787a2b`](https://github.com/juspay/hyperswitch/commit/9787a2becf1bc9eceee6a1fec0a4edb5c3e6473b)) +- **router:** Add payment incoming webhooks support for v2 ([#6551](https://github.com/juspay/hyperswitch/pull/6551)) ([`8e9c3ec`](https://github.com/juspay/hyperswitch/commit/8e9c3ec8931851dae638037b91eb1611399be0bf)) +- **routing:** Add invalidate window as a service for SR based routing ([#6264](https://github.com/juspay/hyperswitch/pull/6264)) ([`607b3df`](https://github.com/juspay/hyperswitch/commit/607b3df3fc822a5f937dbb4f89fbdb0352eca3ff)) + +### Bug Fixes + +- **analytics:** Fix `authentication_type` and `card_last_4` fields serialization for payment_intent_filters ([#6595](https://github.com/juspay/hyperswitch/pull/6595)) ([`0302c30`](https://github.com/juspay/hyperswitch/commit/0302c3033fbff4bfbdb18df44fabc3513b063fb0)) +- **connector:** + - [Worldpay] use 4 digit expiry year ([#6543](https://github.com/juspay/hyperswitch/pull/6543)) ([`e730a2e`](https://github.com/juspay/hyperswitch/commit/e730a2ee5a35d56f3740e923cb16de67edca2fc0)) + - [Adyen]fix error code and message for webhooks response ([#6602](https://github.com/juspay/hyperswitch/pull/6602)) ([`8b31a7b`](https://github.com/juspay/hyperswitch/commit/8b31a7bbe1de88f2126bee4547b37cbb16ea95a4)) +- **docker-compose:** Address "role root does not exist" errors arising from postgres health check ([#6582](https://github.com/juspay/hyperswitch/pull/6582)) ([`e9e8df2`](https://github.com/juspay/hyperswitch/commit/e9e8df222c90661493ba974374d70438ce0ffa6f)) + +### Refactors + +- **payment_methods_v2:** Rename `payment_method` and `payment_method_type` fields and use concrete type for `payment_method_data` ([#6555](https://github.com/juspay/hyperswitch/pull/6555)) ([`11e9241`](https://github.com/juspay/hyperswitch/commit/11e92413b22f13df8cfa62020d48d490e37b5d87)) +- **users:** Force 2FA in production environment ([#6596](https://github.com/juspay/hyperswitch/pull/6596)) ([`bbd55e3`](https://github.com/juspay/hyperswitch/commit/bbd55e32f838349b402e8cd0abc06d34f647be94)) + +**Full Changelog:** [`2024.11.19.0...2024.11.20.0`](https://github.com/juspay/hyperswitch/compare/2024.11.19.0...2024.11.20.0) + +- - - + +## 2024.11.19.0 + +### Features + +- **connector:** [Novalnet] Add support for disputes ([#6560](https://github.com/juspay/hyperswitch/pull/6560)) ([`6881ce2`](https://github.com/juspay/hyperswitch/commit/6881ce2ed3d11006c33fef9863107f0d823ebddb)) +- **payments:** [Payment links] add hide card nickname field config for secure payment links ([#6554](https://github.com/juspay/hyperswitch/pull/6554)) ([`0e026b7`](https://github.com/juspay/hyperswitch/commit/0e026b70b6502c4e82f3e8cccc5441deb472119e)) + +### Refactors + +- **core:** Add profile_id for default_fallback api ([#6546](https://github.com/juspay/hyperswitch/pull/6546)) ([`053f810`](https://github.com/juspay/hyperswitch/commit/053f8109302a98e6b6d30d957b2af618ea73055f)) +- **users:** Make `profile_id` in the JWT non-optional ([#6537](https://github.com/juspay/hyperswitch/pull/6537)) ([`d32397f`](https://github.com/juspay/hyperswitch/commit/d32397f060731f51a15634e221117a554b8b3721)) + +**Full Changelog:** [`2024.11.18.0...2024.11.19.0`](https://github.com/juspay/hyperswitch/compare/2024.11.18.0...2024.11.19.0) + +- - - + +## 2024.11.18.0 + +### Features + +- **payments_v2:** Add finish redirection endpoint ([#6549](https://github.com/juspay/hyperswitch/pull/6549)) ([`0805a93`](https://github.com/juspay/hyperswitch/commit/0805a937b1bc12ac1dfb23922036733ed971a87a)) + +**Full Changelog:** [`2024.11.15.0...2024.11.18.0`](https://github.com/juspay/hyperswitch/compare/2024.11.15.0...2024.11.18.0) + +- - - + +## 2024.11.15.0 + +### Features + +- **analytics:** Add `sessionized_metrics` and `currency_conversion` for refunds analytics ([#6419](https://github.com/juspay/hyperswitch/pull/6419)) ([`afd7f7d`](https://github.com/juspay/hyperswitch/commit/afd7f7d20980f6f39673008c86b89b1e501f05f2)) +- **connector:** [Novalnet] Add supported currencies ([#6547](https://github.com/juspay/hyperswitch/pull/6547)) ([`a35a4f3`](https://github.com/juspay/hyperswitch/commit/a35a4f314242af3c11a27c031388049c8fe4e72d)) +- **themes:** Setup themes table ([#6533](https://github.com/juspay/hyperswitch/pull/6533)) ([`29be1d4`](https://github.com/juspay/hyperswitch/commit/29be1d4fadc55948c99cc8bd33b3b8e8d341ae11)) +- Implement scylla traits for StrongSecret ([#6500](https://github.com/juspay/hyperswitch/pull/6500)) ([`7d73e90`](https://github.com/juspay/hyperswitch/commit/7d73e9095a532aa5c2bb4bf8806fc678460cf8d4)) + +**Full Changelog:** [`2024.11.14.0...2024.11.15.0`](https://github.com/juspay/hyperswitch/compare/2024.11.14.0...2024.11.15.0) + +- - - + +## 2024.11.14.0 + +### Features + +- **connector:** [ADYEN] Integrate Paze ([#6545](https://github.com/juspay/hyperswitch/pull/6545)) ([`b82e742`](https://github.com/juspay/hyperswitch/commit/b82e7429e2db8ef1241a3f6ebe782319f9d1d98b)) +- **core:** Add Mobile Payment (Direct Carrier Billing) as a payment method ([#6196](https://github.com/juspay/hyperswitch/pull/6196)) ([`d0a041c`](https://github.com/juspay/hyperswitch/commit/d0a041c361668d0eff6c9b0dde67351b6ed43d19)) +- **openapi:** Add payment get to openapi ([#6539](https://github.com/juspay/hyperswitch/pull/6539)) ([`600cf44`](https://github.com/juspay/hyperswitch/commit/600cf44684912192f0bf1b9566fd0a7daae9f54c)) +- **users:** Add global support in user roles ([#6458](https://github.com/juspay/hyperswitch/pull/6458)) ([`98b141c`](https://github.com/juspay/hyperswitch/commit/98b141c6a00e6435385e1c513b1684d58567ecee)) + +### Bug Fixes + +- **payments:** Populate payment_method_type in payment_attempt for cards ([#6519](https://github.com/juspay/hyperswitch/pull/6519)) ([`574170a`](https://github.com/juspay/hyperswitch/commit/574170a357fdb0a5134354f29e46d57fa4ea5201)) +- **webhooks:** Add support for updating mandate details in webhooks flow ([#6523](https://github.com/juspay/hyperswitch/pull/6523)) ([`6eb72e9`](https://github.com/juspay/hyperswitch/commit/6eb72e923ee05361d018dcdae837b637fad03d88)) + +### Documentation + +- **analytics:** Add setup instructions for currency_conversion service ([#6516](https://github.com/juspay/hyperswitch/pull/6516)) ([`31a38db`](https://github.com/juspay/hyperswitch/commit/31a38db8005e6e566c3c7330bdcfca0cbdca19eb)) + +**Full Changelog:** [`2024.11.13.0...2024.11.14.0`](https://github.com/juspay/hyperswitch/compare/2024.11.13.0...2024.11.14.0) + +- - - + +## 2024.11.13.0 + +### Features + +- **connector:** [NOMUPAY] Add template code ([#6382](https://github.com/juspay/hyperswitch/pull/6382)) ([`20a3a1c`](https://github.com/juspay/hyperswitch/commit/20a3a1c2d6bb93fb4dae7f7eb669ebd85e631c96)) +- **events:** Add payment reject audit events ([#6465](https://github.com/juspay/hyperswitch/pull/6465)) ([`6b029ab`](https://github.com/juspay/hyperswitch/commit/6b029ab195670f526089a708e7aa807f58a5de7d)) + +### Bug Fixes + +- Trustpay `eps` redirection in cypress ([#6529](https://github.com/juspay/hyperswitch/pull/6529)) ([`7f4f55b`](https://github.com/juspay/hyperswitch/commit/7f4f55b63af86cab11421f8ed2979a4ec90b8a44)) + +### Refactors + +- **routing:** Remove payment_id from dynamic_routing metrics ([#6535](https://github.com/juspay/hyperswitch/pull/6535)) ([`c484beb`](https://github.com/juspay/hyperswitch/commit/c484beb039de4fa2df8d803ad000b4d352ce4c13)) +- Move Payout traits to hyperswitch_interfaces for connectors crate ([#6481](https://github.com/juspay/hyperswitch/pull/6481)) ([`6808272`](https://github.com/juspay/hyperswitch/commit/6808272de305c685b7cf948060f006d39cbac60b)) + +### Documentation + +- **api-reference:** Remove redundant webhooks page ([#6538](https://github.com/juspay/hyperswitch/pull/6538)) ([`548d1b0`](https://github.com/juspay/hyperswitch/commit/548d1b0c0ed9ed21fefbe8bf1289540cb4a7cec1)) +- **openapi:** Fixed API documentation for V2 ([#6496](https://github.com/juspay/hyperswitch/pull/6496)) ([`7dfcd51`](https://github.com/juspay/hyperswitch/commit/7dfcd514cf7c04c92fefc58edfc518dc4eb49bcd)) + +**Full Changelog:** [`2024.11.12.0...2024.11.13.0`](https://github.com/juspay/hyperswitch/compare/2024.11.12.0...2024.11.13.0) + +- - - + +## 2024.11.12.0 + +### Features + +- **payment_v2:** Implement payments sync ([#6464](https://github.com/juspay/hyperswitch/pull/6464)) ([`42bdf47`](https://github.com/juspay/hyperswitch/commit/42bdf47fd295c523e26b91f7ed209239d5c4b1bb)) + +### Refactors + +- Explicitly specify top redirections for secure payment and payout links ([#6494](https://github.com/juspay/hyperswitch/pull/6494)) ([`0a506b1`](https://github.com/juspay/hyperswitch/commit/0a506b1729a27e47543cf24f64fbad08479d8dec)) + +**Full Changelog:** [`2024.11.11.0...2024.11.12.0`](https://github.com/juspay/hyperswitch/compare/2024.11.11.0...2024.11.12.0) + +- - - + +## 2024.11.11.0 + +### Features + +- **analytics:** Revert remove additional filters from PaymentIntentFilters ([#6492](https://github.com/juspay/hyperswitch/pull/6492)) ([`ce95b65`](https://github.com/juspay/hyperswitch/commit/ce95b6538dca4515b04ac65c2b1063bdd0a9c3a7)) +- **connector:** + - [AMAZON PAY] Added Template code ([#6486](https://github.com/juspay/hyperswitch/pull/6486)) ([`fe4931a`](https://github.com/juspay/hyperswitch/commit/fe4931a37e6030ea03ca83540f9a21877c7b6b34)) + - [worldpay] add support for mandates ([#6479](https://github.com/juspay/hyperswitch/pull/6479)) ([`378ec44`](https://github.com/juspay/hyperswitch/commit/378ec44db9752020083d61a538592d5383a06b40)) +- **opensearch:** Refactor global search querybuilder and add case insensitivity opensearch filters ([#6476](https://github.com/juspay/hyperswitch/pull/6476)) ([`529f1a7`](https://github.com/juspay/hyperswitch/commit/529f1a76be2e10759b44e6cfb21a7d43bbc53109)) +- **payments:** + - Add audit events for PaymentApprove update ([#6432](https://github.com/juspay/hyperswitch/pull/6432)) ([`6823418`](https://github.com/juspay/hyperswitch/commit/6823418e2a6416fe964eaf756b6418738a5e74e0)) + - Add audit events for PaymentUpdate update ([#6426](https://github.com/juspay/hyperswitch/pull/6426)) ([`1be2654`](https://github.com/juspay/hyperswitch/commit/1be2654b4fd61a9d6a9e3b3772d9bffd8f1333dc)) +- **router:** Add `start_redirection` api for three_ds flow in v2 ([#6470](https://github.com/juspay/hyperswitch/pull/6470)) ([`6f24bb4`](https://github.com/juspay/hyperswitch/commit/6f24bb4ee349683ea95cd5eb9d682d83c92a637d)) + +### Bug Fixes + +- **connector:** + - [Novalnet] Send decoded wallet token to applepay ([#6503](https://github.com/juspay/hyperswitch/pull/6503)) ([`860a57a`](https://github.com/juspay/hyperswitch/commit/860a57ad9a679056ac66423edfc16973f497e184)) + - [Novalnet] Add mandatory fields for wallets and card in config ([#6463](https://github.com/juspay/hyperswitch/pull/6463)) ([`3d9f443`](https://github.com/juspay/hyperswitch/commit/3d9f4432bcef8a5326d1bdabbc2be5bd0df9fd73)) + - [fiuu]fix mandates for fiuu ([#6487](https://github.com/juspay/hyperswitch/pull/6487)) ([`bc92a2e`](https://github.com/juspay/hyperswitch/commit/bc92a2e9d9bb1ec914670ea1c2e399c9c6b8839a)) +- **docs:** Fix broken pages in API reference ([#6507](https://github.com/juspay/hyperswitch/pull/6507)) ([`21d3071`](https://github.com/juspay/hyperswitch/commit/21d3071f317e153f9ff83446c29b0f88c4bbd973)) +- **router:** + - Get apple pay certificates only from metadata during the session call ([#6514](https://github.com/juspay/hyperswitch/pull/6514)) ([`51b6cdf`](https://github.com/juspay/hyperswitch/commit/51b6cdfad76027f96df1e9f72b4b40ca6f2194c0)) + - Add card expiry check in the `network_transaction_id_and_card_details` based `MIT` flow ([#6504](https://github.com/juspay/hyperswitch/pull/6504)) ([`5af532a`](https://github.com/juspay/hyperswitch/commit/5af532a1212ee0bf91bd485b0c761e38127bb76e)) + +### Refactors + +- **core:** Interpolate success_based_routing config params with their specific values ([#6448](https://github.com/juspay/hyperswitch/pull/6448)) ([`d9ce42f`](https://github.com/juspay/hyperswitch/commit/d9ce42fd0cecb1eda196071da925f4f0e75a834f)) +- **payment_methods:** Refactor customer payment methods list v2 code to follow better code practices ([#6433](https://github.com/juspay/hyperswitch/pull/6433)) ([`0389ae7`](https://github.com/juspay/hyperswitch/commit/0389ae74e112dedd9d98314906820f78e4b89380)) +- **router:** Remove card exp validation for migration api ([#6460](https://github.com/juspay/hyperswitch/pull/6460)) ([`1dfcaab`](https://github.com/juspay/hyperswitch/commit/1dfcaabff8a42c0ceb52215eca558fa1b297a929)) + +### Miscellaneous Tasks + +- Change serde value to strict type in payment intent domain and diesel model ([#6393](https://github.com/juspay/hyperswitch/pull/6393)) ([`a5ac69d`](https://github.com/juspay/hyperswitch/commit/a5ac69d1a77e772e430df8c4187942de44f23079)) + +**Full Changelog:** [`2024.11.08.0...2024.11.11.0`](https://github.com/juspay/hyperswitch/compare/2024.11.08.0...2024.11.11.0) + +- - - + +## 2024.11.08.0 + +### Features + +- **payments:** Add audit events for PaymentCreate update ([#6427](https://github.com/juspay/hyperswitch/pull/6427)) ([`063fe90`](https://github.com/juspay/hyperswitch/commit/063fe904c66c9af3d7ce0a82ad712eac69e41786)) + +**Full Changelog:** [`2024.11.07.1...2024.11.08.0`](https://github.com/juspay/hyperswitch/compare/2024.11.07.1...2024.11.08.0) + +- - - + +## 2024.11.07.1 + +### Bug Fixes + +- **users:** Add force rotate password on first login for non-email flow ([#6483](https://github.com/juspay/hyperswitch/pull/6483)) ([`b43033c`](https://github.com/juspay/hyperswitch/commit/b43033c2d9530d291651326cd987476e4924132b)) + +### Refactors + +- **connector:** Added amount conversion framework to Wise. ([#6469](https://github.com/juspay/hyperswitch/pull/6469)) ([`1ba3d84`](https://github.com/juspay/hyperswitch/commit/1ba3d84df1e93d2286db1a262c4a67b3861b90c0)) + +**Full Changelog:** [`2024.11.07.0...2024.11.07.1`](https://github.com/juspay/hyperswitch/compare/2024.11.07.0...2024.11.07.1) + +- - - + +## 2024.11.07.0 + +### Features + +- **analytics:** Implement currency conversion to power multi-currency aggregation ([#6418](https://github.com/juspay/hyperswitch/pull/6418)) ([`01c5216`](https://github.com/juspay/hyperswitch/commit/01c5216fdd6f1d841082868cccea6054b64e9e07)) + +### Bug Fixes + +- **core:** PMD Not Getting Populated for Saved Card Transactions ([#6497](https://github.com/juspay/hyperswitch/pull/6497)) ([`b8b2060`](https://github.com/juspay/hyperswitch/commit/b8b206057c5b464420a6d115a1116ef5cc695bf7)) + +**Full Changelog:** [`2024.11.06.0...2024.11.07.0`](https://github.com/juspay/hyperswitch/compare/2024.11.06.0...2024.11.07.0) + +- - - + +## 2024.11.06.0 + +### Features + +- **config:** Update vector config ([#6365](https://github.com/juspay/hyperswitch/pull/6365)) ([`2919db8`](https://github.com/juspay/hyperswitch/commit/2919db874bd84372663228f2531ba18338e039c0)) +- **connector:** + - [ELAVON] Template PR ([#6309](https://github.com/juspay/hyperswitch/pull/6309)) ([`b481e5c`](https://github.com/juspay/hyperswitch/commit/b481e5cb8ffe417591a2fb917f37ba72667f2fcd)) + - [Paypal] implement vaulting for paypal wallet and cards while purchasing ([#5323](https://github.com/juspay/hyperswitch/pull/5323)) ([`22ba2db`](https://github.com/juspay/hyperswitch/commit/22ba2dbb2870471315d688147b3b53c432ce15dc)) + - [JP MORGAN] Added Template code for cards integration ([#6467](https://github.com/juspay/hyperswitch/pull/6467)) ([`b048e39`](https://github.com/juspay/hyperswitch/commit/b048e39b5c4213752da7765834915cca6bf776f6)) +- **db:** Implement `MerchantAccountInteraface` for `Mockdb` ([#6283](https://github.com/juspay/hyperswitch/pull/6283)) ([`5f493a5`](https://github.com/juspay/hyperswitch/commit/5f493a5166aa0a0a29f9aed538cad03def657c22)) +- **nix:** Add support for running external services through services-flake ([#6377](https://github.com/juspay/hyperswitch/pull/6377)) ([`95f2e0b`](https://github.com/juspay/hyperswitch/commit/95f2e0b8c51bfe116241fc486069e10e578a5ff8)) +- **users:** Add `force_two_factor_auth` environment variable ([#6466](https://github.com/juspay/hyperswitch/pull/6466)) ([`6b66ccc`](https://github.com/juspay/hyperswitch/commit/6b66cccd02c2589bb2dad38b46f4da7e1455ca0b)) + +### Bug Fixes + +- **connector:** + - Expiration Year Incorrectly Populated as YYYY Format in Paybox Mandates ([#6474](https://github.com/juspay/hyperswitch/pull/6474)) ([`e457ccd`](https://github.com/juspay/hyperswitch/commit/e457ccd91e60d5168e0a3283dfa325097f455076)) + - [Cybersource] remove newline in billing address with space ([#6478](https://github.com/juspay/hyperswitch/pull/6478)) ([`7f1d345`](https://github.com/juspay/hyperswitch/commit/7f1d34571f72f63b8bb52aff995ad093e3b6d856)) +- **refunds:** Remove to schema from refund aggregate response and exclude it from open api documentation ([#6405](https://github.com/juspay/hyperswitch/pull/6405)) ([`449c9cf`](https://github.com/juspay/hyperswitch/commit/449c9cfe557b3540e4ad25e48e012b531eb232fd)) +- Replace deprecated backticks with $(...) for command substitution ([#6337](https://github.com/juspay/hyperswitch/pull/6337)) ([`1c92f58`](https://github.com/juspay/hyperswitch/commit/1c92f5843009db42778f94bc9fd915b411a93f76)) +- Lazy connection pools for dynamic routing service ([#6437](https://github.com/juspay/hyperswitch/pull/6437)) ([`71d9933`](https://github.com/juspay/hyperswitch/commit/71d99332204ddfbb3cf305c7d3bc8840d508bf47)) + +**Full Changelog:** [`2024.11.05.0...2024.11.06.0`](https://github.com/juspay/hyperswitch/compare/2024.11.05.0...2024.11.06.0) + +- - - + +## 2024.11.05.0 + +### Features + +- Add macro to generate ToEncryptable trait ([#6313](https://github.com/juspay/hyperswitch/pull/6313)) ([`19cf0f7`](https://github.com/juspay/hyperswitch/commit/19cf0f7437a8d16ee4da254d2a3e2659879be68c)) + +### Bug Fixes + +- **analytics:** Add dynamic limit by clause in failure reasons metric query ([#6462](https://github.com/juspay/hyperswitch/pull/6462)) ([`8825378`](https://github.com/juspay/hyperswitch/commit/88253780d708bc1c005a87c186c4b0b14325c8a0)) + +### Refactors + +- **connector:** [AIRWALLEX, MULTISAFEPAY, RAZORPAY, SHIFT4, WORLDPAY, ZSL] Move connectors from `router` to `hyperswitch_connectors` crate ([#6369](https://github.com/juspay/hyperswitch/pull/6369)) ([`72ee434`](https://github.com/juspay/hyperswitch/commit/72ee434003eef744d516343a2f803264f226d92a)) + +**Full Changelog:** [`2024.11.04.0...2024.11.05.0`](https://github.com/juspay/hyperswitch/compare/2024.11.04.0...2024.11.05.0) + +- - - + +## 2024.11.04.0 + +### Features + +- **analytics:** Add `customer_id` as filter for payment intents ([#6344](https://github.com/juspay/hyperswitch/pull/6344)) ([`d697def`](https://github.com/juspay/hyperswitch/commit/d697def0b7cad3743db9fd70d09a45921dcbea61)) +- **authz:** Make info APIs support `ParentGroup` ([#6440](https://github.com/juspay/hyperswitch/pull/6440)) ([`7dcffcc`](https://github.com/juspay/hyperswitch/commit/7dcffccf3f16de5e40f61a302beb318035c3e88b)) +- **connector:** [Paybox] Add mandates Flow for Paybox ([#6378](https://github.com/juspay/hyperswitch/pull/6378)) ([`37513e0`](https://github.com/juspay/hyperswitch/commit/37513e0f1e78f99da0accf0fee263c10ca4e03c6)) +- **cypress-test:** Include worldpay's request / response structure for test suite ([#6420](https://github.com/juspay/hyperswitch/pull/6420)) ([`8372389`](https://github.com/juspay/hyperswitch/commit/8372389671c4aefeb625365d198390df5d8f35a5)) +- **router:** Add payments get-intent API for v2 ([#6396](https://github.com/juspay/hyperswitch/pull/6396)) ([`c514608`](https://github.com/juspay/hyperswitch/commit/c514608594ebbe9894de47747b0d9fb573ab2503)) + +### Refactors + +- **connector:** Add amount conversion framework to rapyd ([#6414](https://github.com/juspay/hyperswitch/pull/6414)) ([`33bc83f`](https://github.com/juspay/hyperswitch/commit/33bc83fce47c579457f1b9be0a91bb4fa13585ff)) +- **connnector:** Structure connector enums in separate files for improved team ownership ([#6459](https://github.com/juspay/hyperswitch/pull/6459)) ([`bb246e2`](https://github.com/juspay/hyperswitch/commit/bb246e27b72e9e4168c89b94e8d07d63a544b586)) + +### Documentation + +- **README:** Updated the icon and repositioned the hero image ([#6445](https://github.com/juspay/hyperswitch/pull/6445)) ([`35bf5a9`](https://github.com/juspay/hyperswitch/commit/35bf5a91d9a5b2d5e476c995e679b445242218e0)) + +### Miscellaneous Tasks + +- **users:** Change entity_type column of roles to non-optional ([#6435](https://github.com/juspay/hyperswitch/pull/6435)) ([`62067e4`](https://github.com/juspay/hyperswitch/commit/62067e406a01d3a17ef94a04b0ef0304ebd05a70)) + +**Full Changelog:** [`2024.10.30.0...2024.11.04.0`](https://github.com/juspay/hyperswitch/compare/2024.10.30.0...2024.11.04.0) + +- - - + +## 2024.10.30.0 + +### Refactors + +- **connector:** Add amount conversion framework to payu ([#6199](https://github.com/juspay/hyperswitch/pull/6199)) ([`11ce389`](https://github.com/juspay/hyperswitch/commit/11ce389000bf53c7f740d069f7ad2262bf5b70d6)) + +### Documentation + +- Added desc. for wallets other than AP, GP ([#6452](https://github.com/juspay/hyperswitch/pull/6452)) ([`55a81eb`](https://github.com/juspay/hyperswitch/commit/55a81eb4692979036d0bfd43e445d3e1db6601e7)) + +**Full Changelog:** [`2024.10.29.0...2024.10.30.0`](https://github.com/juspay/hyperswitch/compare/2024.10.29.0...2024.10.30.0) + +- - - + +## 2024.10.29.0 + +### Bug Fixes + +- **multitenancy:** Consistently use tenant nomenclature everywhere ([#6389](https://github.com/juspay/hyperswitch/pull/6389)) ([`aecd5ee`](https://github.com/juspay/hyperswitch/commit/aecd5eea3d2dce3ccdd4784f60d076b641104b67)) + +**Full Changelog:** [`2024.10.28.2...2024.10.29.0`](https://github.com/juspay/hyperswitch/compare/2024.10.28.2...2024.10.29.0) + +- - - + +## 2024.10.28.2 + +### Bug Fixes + +- **connector:** + - [Novalnet] Remove webhook placeholder connector config ([#6451](https://github.com/juspay/hyperswitch/pull/6451)) ([`e33340e`](https://github.com/juspay/hyperswitch/commit/e33340e70b59e9e4f18e92fc27d8c90b3df5768b)) + - [Adyen] Add MYR currency config ([#6442](https://github.com/juspay/hyperswitch/pull/6442)) ([`925e424`](https://github.com/juspay/hyperswitch/commit/925e4240e4ad6da1d243769b184842c0d8251a7d)) + +**Full Changelog:** [`2024.10.28.1...2024.10.28.2`](https://github.com/juspay/hyperswitch/compare/2024.10.28.1...2024.10.28.2) + +- - - + +## 2024.10.28.1 + +### Bug Fixes + +- **core:** Fix setup mandate payments to store connector mandate details ([#6446](https://github.com/juspay/hyperswitch/pull/6446)) ([`cee84cd`](https://github.com/juspay/hyperswitch/commit/cee84cdcfd6c323e8db80163f462d8e286aae600)) + +**Full Changelog:** [`2024.10.28.0...2024.10.28.1`](https://github.com/juspay/hyperswitch/compare/2024.10.28.0...2024.10.28.1) + +- - - + +## 2024.10.28.0 + +### Features + +- **connector:** + - [Rapyd] Use connector_response_reference_id ([#6302](https://github.com/juspay/hyperswitch/pull/6302)) ([`a845d46`](https://github.com/juspay/hyperswitch/commit/a845d46899d87ba7f3ca4386719c1934ce3da90e)) + - [Rapyd] Use connector_request_reference_id ([#6296](https://github.com/juspay/hyperswitch/pull/6296)) ([`4105d98`](https://github.com/juspay/hyperswitch/commit/4105d98d7aca885f9c622d5b56c6dbacb85a688b)) + - [Novalnet] Integrate Applepay wallet token flow ([#6409](https://github.com/juspay/hyperswitch/pull/6409)) ([`1d24b04`](https://github.com/juspay/hyperswitch/commit/1d24b04596e6d2f7c44b93501d56fc4fb950bd3b)) + - [PayU] Use connector_request_reference_id ([#6360](https://github.com/juspay/hyperswitch/pull/6360)) ([`acd1530`](https://github.com/juspay/hyperswitch/commit/acd153042062dd14d5e6e266fdc73d82b78213fe)) + - [Fiuu] Add support for cards recurring payments ([#6361](https://github.com/juspay/hyperswitch/pull/6361)) ([`4647a2f`](https://github.com/juspay/hyperswitch/commit/4647a2f6aece6b9479395fa3622b51b50d3091ee)) +- **euclid:** Add dynamic routing in core flows ([#6333](https://github.com/juspay/hyperswitch/pull/6333)) ([`ce732db`](https://github.com/juspay/hyperswitch/commit/ce732db9b2f98924a2b1d44ea5eb1000b6cbb498)) +- **router:** Move organization_id to request header from request body for v2 ([#6277](https://github.com/juspay/hyperswitch/pull/6277)) ([`aaac9aa`](https://github.com/juspay/hyperswitch/commit/aaac9aa97d1b00d50bec4e02efb0658956463398)) +- **sample_data:** Generate random disputes for sample data ([#6341](https://github.com/juspay/hyperswitch/pull/6341)) ([`e36ea18`](https://github.com/juspay/hyperswitch/commit/e36ea184ae6d1363fb1af55c790162df9f8b451c)) +- Add amount, currency and email to paze session response ([#6412](https://github.com/juspay/hyperswitch/pull/6412)) ([`a3ea62f`](https://github.com/juspay/hyperswitch/commit/a3ea62f88524a360b666cacfbc1cf239f6be8797)) + +### Bug Fixes + +- **analytics:** Fix refund status filter on dashboard ([#6431](https://github.com/juspay/hyperswitch/pull/6431)) ([`d58f706`](https://github.com/juspay/hyperswitch/commit/d58f706dc3fdd5ea277eeef6de9c224fe6097b46)) +- **router:** Update request body for migrate-batch api ([#6429](https://github.com/juspay/hyperswitch/pull/6429)) ([`5307579`](https://github.com/juspay/hyperswitch/commit/53075792b372a7ca574b94058c7d72033c014bc8)) + +### Refactors + +- **connector:** + - Add amount conversion framework to tsys ([#6282](https://github.com/juspay/hyperswitch/pull/6282)) ([`90765be`](https://github.com/juspay/hyperswitch/commit/90765bece1b12b208192e7ae4d54f4c70a301cea)) + - [Paypal] Add support for passing shipping_cost in Payment request ([#6423](https://github.com/juspay/hyperswitch/pull/6423)) ([`b0d5c96`](https://github.com/juspay/hyperswitch/commit/b0d5c96b9918549663125681259a598698ec705c)) + - Added amount conversion framework for klarna and change type of amount to MinorUnit for OrderDetailsWithAmount ([#4979](https://github.com/juspay/hyperswitch/pull/4979)) ([`2807622`](https://github.com/juspay/hyperswitch/commit/2807622ba671f77892a0fde42febbcffcb6c2238)) + +**Full Changelog:** [`2024.10.25.0...2024.10.28.0`](https://github.com/juspay/hyperswitch/compare/2024.10.25.0...2024.10.28.0) + +- - - + +## 2024.10.25.0 + +### Features + +- **authz:** Create a permission generator ([#6394](https://github.com/juspay/hyperswitch/pull/6394)) ([`4a0afb8`](https://github.com/juspay/hyperswitch/commit/4a0afb8213cce47cabe9e3f5d22ad1dccb02c20f)) +- **connector:** + - [Airwallex] Use connector_response_reference_id as reference to merchant ([#2747](https://github.com/juspay/hyperswitch/pull/2747)) ([`4b569c9`](https://github.com/juspay/hyperswitch/commit/4b569c9d5eb9b6403175c958b887d7ace4d9cbbb)) + - [Novalnet] Integrate wallets Paypal and Googlepay ([#6370](https://github.com/juspay/hyperswitch/pull/6370)) ([`673b869`](https://github.com/juspay/hyperswitch/commit/673b8691e092e145ba211050db4f5c7e021a0ce2)) +- **payments_v2:** Add payment_confirm_intent api endpoint ([#6263](https://github.com/juspay/hyperswitch/pull/6263)) ([`c7c1e1a`](https://github.com/juspay/hyperswitch/commit/c7c1e1adabceeb0a03659bf8feb9aa06d85960ea)) + +### Bug Fixes + +- **core:** Populate billing_address for payment with pm_id ([#6411](https://github.com/juspay/hyperswitch/pull/6411)) ([`8e58b56`](https://github.com/juspay/hyperswitch/commit/8e58b56b43ad2f823c51943c34aa8837297c70d6)) +- **payment_methods:** Fix merchant payment method list to retain a mca based on connector_name and mca_id ([#6408](https://github.com/juspay/hyperswitch/pull/6408)) ([`842c4a2`](https://github.com/juspay/hyperswitch/commit/842c4a2f47d4cc7b850a16abbe5431fe575f7a86)) +- **payments:** Filter total count by card-network value ([#6397](https://github.com/juspay/hyperswitch/pull/6397)) ([`ca325e9`](https://github.com/juspay/hyperswitch/commit/ca325e969b24fbbb5aa7edcdf86d5b3022291db1)) + +### Refactors + +- **connector:** + - Add amount conversion framework to Shift4 ([#6250](https://github.com/juspay/hyperswitch/pull/6250)) ([`fbe3951`](https://github.com/juspay/hyperswitch/commit/fbe395198aea7252e9c4e3fad97956a548d07002)) + - Add amount conversion framework to Wellsfargo ([#6298](https://github.com/juspay/hyperswitch/pull/6298)) ([`c3b0f7c`](https://github.com/juspay/hyperswitch/commit/c3b0f7c1d6ad95034535048aa50ff6abe9ed6aa0)) + +### Documentation + +- **cypress:** Refactor cypress documentation for more clarity ([#6415](https://github.com/juspay/hyperswitch/pull/6415)) ([`26e0c32`](https://github.com/juspay/hyperswitch/commit/26e0c32f4da5689a1c01fbb456ac008a0b831710)) +- **openapi:** Improve `rust_locker_open_api_spec` ([#6322](https://github.com/juspay/hyperswitch/pull/6322)) ([`a31d164`](https://github.com/juspay/hyperswitch/commit/a31d1641fb9e1c9efd652c6f191f6b29c75dc69b)) + +### Miscellaneous Tasks + +- Add samsung pay payment method support for cybersource ([#6424](https://github.com/juspay/hyperswitch/pull/6424)) ([`ecaf700`](https://github.com/juspay/hyperswitch/commit/ecaf70099671950287e9a6b7d30ffd02c0c5f51e)) +- Address Rust 1.82.0 clippy lints ([#6401](https://github.com/juspay/hyperswitch/pull/6401)) ([`8708a5c`](https://github.com/juspay/hyperswitch/commit/8708a5cb8f7d64a382b2fe061c725d4854ba9e92)) + +**Full Changelog:** [`2024.10.24.0...2024.10.25.0`](https://github.com/juspay/hyperswitch/compare/2024.10.24.0...2024.10.25.0) + +- - - + +## 2024.10.24.0 + +### Features + +- **analytics:** Remove additional filters from PaymentIntentFilters ([#6403](https://github.com/juspay/hyperswitch/pull/6403)) ([`4ef48c3`](https://github.com/juspay/hyperswitch/commit/4ef48c39b3ed7c1fcda9c850da766a0bdb701335)) +- **router:** Add api_models and openapi changes for refunds create api v2 ([#6385](https://github.com/juspay/hyperswitch/pull/6385)) ([`5a10e58`](https://github.com/juspay/hyperswitch/commit/5a10e5867a0f3097a40c8a6868454ff06630ed2c)) + +### Bug Fixes + +- **connector_config:** Include the `payment_processing_details_at` `Hyperswitch` option only if apple pay token decryption flow is supported for the connector ([#6386](https://github.com/juspay/hyperswitch/pull/6386)) ([`af0aeee`](https://github.com/juspay/hyperswitch/commit/af0aeeea53014d8fe5c955cbad3fe8b371c44889)) +- **deployment-config:** Remove invalid currencies from worldpay filters ([#6400](https://github.com/juspay/hyperswitch/pull/6400)) ([`aee11c5`](https://github.com/juspay/hyperswitch/commit/aee11c560e427195a0d321dff19c0d33ec60ba64)) + +### Refactors + +- **connector:** Move connectors Forte, Nexinets, Payeezy, Payu and Zen from Router to Hyperswitch Connector Trait ([#6261](https://github.com/juspay/hyperswitch/pull/6261)) ([`829a20c`](https://github.com/juspay/hyperswitch/commit/829a20cc933267551e49565d06eb08e03e5f13bb)) + +**Full Changelog:** [`2024.10.23.0...2024.10.24.0`](https://github.com/juspay/hyperswitch/compare/2024.10.23.0...2024.10.24.0) + +- - - + +## 2024.10.23.0 + +### Features + +- **cypress:** Execute cypress tests in parallel ([#6225](https://github.com/juspay/hyperswitch/pull/6225)) ([`f247978`](https://github.com/juspay/hyperswitch/commit/f24797834553794f341bd4f3be3afe5fcba693ed)) + +### Refactors + +- **connector:** [WorldPay] propagate refusal codes as error code and messages ([#6392](https://github.com/juspay/hyperswitch/pull/6392)) ([`3d1a3cd`](https://github.com/juspay/hyperswitch/commit/3d1a3cdc8f942a3dca2e6a200bf9200366bd62f1)) +- **permissions:** Remove permissions field from permission info API response ([#6376](https://github.com/juspay/hyperswitch/pull/6376)) ([`e5710fa`](https://github.com/juspay/hyperswitch/commit/e5710fa084ed5b0a4969a63b14a7f8e3433a3c64)) + +**Full Changelog:** [`2024.10.22.0...2024.10.23.0`](https://github.com/juspay/hyperswitch/compare/2024.10.22.0...2024.10.23.0) + +- - - + +## 2024.10.22.0 + +### Features + +- **connector:** Add 3DS flow for Worldpay ([#6374](https://github.com/juspay/hyperswitch/pull/6374)) ([`b93c849`](https://github.com/juspay/hyperswitch/commit/b93c849623c46ad00fe8dfe5bed85a43c700b3c8)) + +### Bug Fixes + +- **mandates:** Allow connector_mandate_detail updation in case of 'Authorized' Payments ([#6379](https://github.com/juspay/hyperswitch/pull/6379)) ([`d09a805`](https://github.com/juspay/hyperswitch/commit/d09a805c0ab4e1224a94ef64b0d75a77355bc3f3)) + +### Refactors + +- **connector:** [WorldPay] migrate from modular to standard payment APIs ([#6317](https://github.com/juspay/hyperswitch/pull/6317)) ([`58296ff`](https://github.com/juspay/hyperswitch/commit/58296ffae6ff6f2f2c8f7b23dd28e92b374b9be3)) +- **router:** Introduce ApiKeyId id type ([#6324](https://github.com/juspay/hyperswitch/pull/6324)) ([`b3ce373`](https://github.com/juspay/hyperswitch/commit/b3ce373f8ecdce362296c9a4b3c3e3543e1baa6f)) + +**Full Changelog:** [`2024.10.21.0...2024.10.22.0`](https://github.com/juspay/hyperswitch/compare/2024.10.21.0...2024.10.22.0) + +- - - + +## 2024.10.21.0 + +### Features + +- **opensearch:** Add additional global search filters and create sessionizer indexes for local ([#6352](https://github.com/juspay/hyperswitch/pull/6352)) ([`2e6cd6d`](https://github.com/juspay/hyperswitch/commit/2e6cd6d31e4e3168b97427de936724de94df6415)) + +### Bug Fixes + +- **router:** Make `x_merchant_domain` as required value only for session call done on web ([#6362](https://github.com/juspay/hyperswitch/pull/6362)) ([`ba6f7a8`](https://github.com/juspay/hyperswitch/commit/ba6f7a817ba3eeb8b3d6304ddd5b2baaf55733e8)) + +### Refactors + +- **connector:** + - Added amount conversion framework for Mollie ([#6280](https://github.com/juspay/hyperswitch/pull/6280)) ([`451376e`](https://github.com/juspay/hyperswitch/commit/451376e7993839f5c93624c12833af7d47aa4e34)) + - [Billwerk] Move connector Billwerk form Router to HyperswitchConnector Crate ([#6266](https://github.com/juspay/hyperswitch/pull/6266)) ([`3cf6210`](https://github.com/juspay/hyperswitch/commit/3cf6210176b2ecc4537b7537a28ea4c87a553794)) + - Add amount conversion framework to opayo ([#6342](https://github.com/juspay/hyperswitch/pull/6342)) ([`91146de`](https://github.com/juspay/hyperswitch/commit/91146de2a2bc684998023535e56dee1af92fda76)) +- **core:** Populate shipping_cost in payment response ([#6351](https://github.com/juspay/hyperswitch/pull/6351)) ([`368e6b5`](https://github.com/juspay/hyperswitch/commit/368e6b53109890ca44bc352dd07ee542791e50df)) +- **users:** Update Database connection for Read only functions ([#6167](https://github.com/juspay/hyperswitch/pull/6167)) ([`fba4a02`](https://github.com/juspay/hyperswitch/commit/fba4a027dfe1c514867c54dba32079dff63609a9)) + +### Documentation + +- Upload new logos ([#6368](https://github.com/juspay/hyperswitch/pull/6368)) ([`0bda934`](https://github.com/juspay/hyperswitch/commit/0bda934aca6bc53b21ab3c2be2af27219ef4f68a)) + +**Full Changelog:** [`2024.10.18.0...2024.10.21.0`](https://github.com/juspay/hyperswitch/compare/2024.10.18.0...2024.10.21.0) + +- - - + +## 2024.10.18.0 + +### Features + +- **router:** Add payments create-intent flow for v2 ([#6193](https://github.com/juspay/hyperswitch/pull/6193)) ([`afa803e`](https://github.com/juspay/hyperswitch/commit/afa803e0f9711f83b31ce53a59e867517a885963)) +- **worldpay:** Migrate to v7 ([#6109](https://github.com/juspay/hyperswitch/pull/6109)) ([`962afbd`](https://github.com/juspay/hyperswitch/commit/962afbd084458e9afb11a0278a8210edd9226a3d)) + +### Bug Fixes + +- **mandates:** Handle the connector_mandate creation once and only if the payment is charged ([#6327](https://github.com/juspay/hyperswitch/pull/6327)) ([`e14a0fe`](https://github.com/juspay/hyperswitch/commit/e14a0fe8f290a697126756ba2facc58234e5d135)) +- **payments_list:** Skip count query if no filters and add logging ([#6331](https://github.com/juspay/hyperswitch/pull/6331)) ([`df2501c`](https://github.com/juspay/hyperswitch/commit/df2501ceafab6180e867953f7c298a541fcea757)) +- **router:** Set the eligible connector in the payment attempt for nti based mit flow ([#6347](https://github.com/juspay/hyperswitch/pull/6347)) ([`1a3d0a6`](https://github.com/juspay/hyperswitch/commit/1a3d0a60f4e3b07786460621c14c5aa37510b53a)) +- **users:** Add max wrong attempts for two-fa ([#6247](https://github.com/juspay/hyperswitch/pull/6247)) ([`2798f57`](https://github.com/juspay/hyperswitch/commit/2798f575605cc4439166344e57ff19b612f1304a)) +- Set headers as optional in ob flows ([#6305](https://github.com/juspay/hyperswitch/pull/6305)) ([`9576ee3`](https://github.com/juspay/hyperswitch/commit/9576ee37a6468d79afc4be280749a2176a95e63b)) + +**Full Changelog:** [`2024.10.17.0...2024.10.18.0`](https://github.com/juspay/hyperswitch/compare/2024.10.17.0...2024.10.18.0) + +- - - + +## 2024.10.17.0 + +### Features + +- **connector:** [fiuu] Add support for payment and refund webhooks ([#6315](https://github.com/juspay/hyperswitch/pull/6315)) ([`d04a87b`](https://github.com/juspay/hyperswitch/commit/d04a87be9e763034c070686cf1c2c73045650d4a)) +- **sample_data:** Extend the batch sample data interface trait for disputes ([#6293](https://github.com/juspay/hyperswitch/pull/6293)) ([`1b31c57`](https://github.com/juspay/hyperswitch/commit/1b31c57fd961d6cec5d8ef1403bf501d1dd74b52)) +- **user_role:** Add limit to `generic_user_roles_list_for_org_and_extra` ([#6191](https://github.com/juspay/hyperswitch/pull/6191)) ([`6aa6b7b`](https://github.com/juspay/hyperswitch/commit/6aa6b7bdc64a367ffaec97fa3826da6e2431ff9d)) + +### Refactors + +- **cypress:** Reuse config update command ([#6197](https://github.com/juspay/hyperswitch/pull/6197)) ([`da194f3`](https://github.com/juspay/hyperswitch/commit/da194f34c6860af04d83ef69041f9e79249454ae)) +- **users:** Move hardcoded email subjects to constants ([#6110](https://github.com/juspay/hyperswitch/pull/6110)) ([`899ec23`](https://github.com/juspay/hyperswitch/commit/899ec23565f99daaad821c1ec1482b4c0cc408c5)) + +### Documentation + +- Simplify README ([#6306](https://github.com/juspay/hyperswitch/pull/6306)) ([`b377227`](https://github.com/juspay/hyperswitch/commit/b3772272678dd9e93b7afc7958b3344cbfe64708)) + +**Full Changelog:** [`2024.10.16.0...2024.10.17.0`](https://github.com/juspay/hyperswitch/compare/2024.10.16.0...2024.10.17.0) + +- - - + +## 2024.10.16.0 + +### Features + +- **core:** Add payments post_session_tokens flow ([#6202](https://github.com/juspay/hyperswitch/pull/6202)) ([`53e82c3`](https://github.com/juspay/hyperswitch/commit/53e82c3faef3ee629a38180e3882a2920332a9a8)) +- **router:** Implement post_update_tracker for SessionUpdate Flow and add support for session_update_flow for Paypal ([#6299](https://github.com/juspay/hyperswitch/pull/6299)) ([`7e90031`](https://github.com/juspay/hyperswitch/commit/7e90031c68c7b93db996ee03e11c56b56a87402b)) + +### Documentation + +- **README:** Remove FAQs section ([#6297](https://github.com/juspay/hyperswitch/pull/6297)) ([`d06d19f`](https://github.com/juspay/hyperswitch/commit/d06d19fc96e1a74d20e2fe3613f86d541947e0ae)) +- **error_codes:** Add unified error codes ([#6319](https://github.com/juspay/hyperswitch/pull/6319)) ([`342529e`](https://github.com/juspay/hyperswitch/commit/342529e0565baaa02f33266c3be620a9561048c8)) + +**Full Changelog:** [`2024.10.15.0...2024.10.16.0`](https://github.com/juspay/hyperswitch/compare/2024.10.15.0...2024.10.16.0) + +- - - + +## 2024.10.15.0 + +### Features + +- **analytics:** Add metrics, filters and APIs for Analytics v2 Dashboard - Payments Page ([#5870](https://github.com/juspay/hyperswitch/pull/5870)) ([`f123df9`](https://github.com/juspay/hyperswitch/commit/f123df9aa31c45b417224af73c2a98325984b3dd)) +- **connector:** [CYBERSOURCE] Add paze dashboard configs ([#6304](https://github.com/juspay/hyperswitch/pull/6304)) ([`df280f2`](https://github.com/juspay/hyperswitch/commit/df280f2574ac701a5e32b9bcae90c87cab7bc5aa)) +- **payment_methods_v2:** Delete payment method api ([#6211](https://github.com/juspay/hyperswitch/pull/6211)) ([`8e538cd`](https://github.com/juspay/hyperswitch/commit/8e538cd6b3da4a155c55ce153982bff3c59ef575)) +- **payments:** Support for card_network filter in payments list ([#5994](https://github.com/juspay/hyperswitch/pull/5994)) ([`1ac8c92`](https://github.com/juspay/hyperswitch/commit/1ac8c92c4bd2259cdd8bf755210bcb3c0eb31472)) +- **router:** Add support for Samsung pay app tokens flow ([#6257](https://github.com/juspay/hyperswitch/pull/6257)) ([`f6b0b98`](https://github.com/juspay/hyperswitch/commit/f6b0b98e0a6c07308b481715f7c9ad063a5f0de9)) + +### Bug Fixes + +- **router:** + - Update nick_name only if card_token.card_holder_name is non empty and populate additional card_details from payment_attempt if not present in the locker ([#6308](https://github.com/juspay/hyperswitch/pull/6308)) ([`9da9c5e`](https://github.com/juspay/hyperswitch/commit/9da9c5e0ffe219a0bf6e08281b87c77eeb5c4575)) + - Replace underscore by hyphen in Samsung pay session call ([#6311](https://github.com/juspay/hyperswitch/pull/6311)) ([`7f1bbbf`](https://github.com/juspay/hyperswitch/commit/7f1bbbfffecb74555756b0003d6a0ae940e581db)) + +### Refactors + +- **connector:** [Adyen platform] api contract change for webhook ([#6281](https://github.com/juspay/hyperswitch/pull/6281)) ([`5b4a1d5`](https://github.com/juspay/hyperswitch/commit/5b4a1d5f6d7b4143116c5f1faf6cb325e4368e6d)) +- Add user agent header in outgoing webhooks ([#6289](https://github.com/juspay/hyperswitch/pull/6289)) ([`fe62b1f`](https://github.com/juspay/hyperswitch/commit/fe62b1fe2137de456a6a0e8e315fd0592c29577d)) + +### Documentation + +- **v2:** Added 'X-Merchant-Id' to headers in Profile API docs ([#6291](https://github.com/juspay/hyperswitch/pull/6291)) ([`ca086d0`](https://github.com/juspay/hyperswitch/commit/ca086d0b25ee12419ebcb7250b4a6678cc33a8a6)) + +**Full Changelog:** [`2024.10.11.0...2024.10.15.0`](https://github.com/juspay/hyperswitch/compare/2024.10.11.0...2024.10.15.0) + +- - - + +## 2024.10.11.0 + +### Features + +- **router:** Add network transaction id support for mit payments ([#6245](https://github.com/juspay/hyperswitch/pull/6245)) ([`ba75a3f`](https://github.com/juspay/hyperswitch/commit/ba75a3f5a936ec981422bbe3c4fbdd9f12928615)) + +### Refactors + +- Refactor(router): modify `net_amount` to be a struct in the domain model of payment_attempt and handle amount changes across all flows ([#6252](https://github.com/juspay/hyperswitch/pull/6252)) ([`5930089`](https://github.com/juspay/hyperswitch/commit/5930089682f89e1cc3e14720fcfa31de43353686)) + +**Full Changelog:** [`2024.10.10.0...2024.10.11.0`](https://github.com/juspay/hyperswitch/compare/2024.10.10.0...2024.10.11.0) + +- - - + + +## 2024.10.09.0 + +### Features + +- **connector:** + - [Novalnet] add webhooks for card ([#6033](https://github.com/juspay/hyperswitch/pull/6033)) ([`d61ebef`](https://github.com/juspay/hyperswitch/commit/d61ebef14908473458ae5962a63b035ddd0b3d94)) + - Integrate PAZE Wallet ([#6030](https://github.com/juspay/hyperswitch/pull/6030)) ([`535f2f1`](https://github.com/juspay/hyperswitch/commit/535f2f12f825be384a17fba8628d8517027bb6c6)) + +### Bug Fixes + +- **connector:** + - [deutsche bank] add support for sepa one-off payments ([#6246](https://github.com/juspay/hyperswitch/pull/6246)) ([`4e07fe9`](https://github.com/juspay/hyperswitch/commit/4e07fe9e8a8ccc19d9e247f4da787c5bf04411ca)) + - [Stripe] fix cashapp webhooks response deserialization failure ([#5690](https://github.com/juspay/hyperswitch/pull/5690)) ([`2ccce01`](https://github.com/juspay/hyperswitch/commit/2ccce01bf4c65559fd085dbb3ab32ef646998c17)) + - Remove placeholder from novalnet webhooks secret ([#6268](https://github.com/juspay/hyperswitch/pull/6268)) ([`86a43b9`](https://github.com/juspay/hyperswitch/commit/86a43b9bc41a358fe133c9fdea8dde09d4965c98)) +- **euclid_wasm:** Update dependency for wasm in validate.rs ([#6262](https://github.com/juspay/hyperswitch/pull/6262)) ([`cc7c17f`](https://github.com/juspay/hyperswitch/commit/cc7c17f873efbe2818bcc472c0e2add3c836e71d)) +- **users:** Allow accepting invites for `org_admin`s ([#6253](https://github.com/juspay/hyperswitch/pull/6253)) ([`2bc21cf`](https://github.com/juspay/hyperswitch/commit/2bc21cfc5e3e6d8403bec82fde14cfd01536f406)) + +**Full Changelog:** [`2024.10.08.0...2024.10.09.0`](https://github.com/juspay/hyperswitch/compare/2024.10.08.0...2024.10.09.0) + +- - - + +## 2024.10.08.0 + +### Bug Fixes + +- **user_role:** Restrict updating user role to the same `EntityType` ([#6224](https://github.com/juspay/hyperswitch/pull/6224)) ([`b499287`](https://github.com/juspay/hyperswitch/commit/b499287f2347837bc885387bb5eb99b3fd841f63)) +- **users:** Trustpay refund url update ([#6251](https://github.com/juspay/hyperswitch/pull/6251)) ([`f4830eb`](https://github.com/juspay/hyperswitch/commit/f4830ebaae5dcc8407e420dfeb1ca981c08bc8fb)) + +### Refactors + +- **dynamic_fields:** Rename sepa in dynamic fields ([#6234](https://github.com/juspay/hyperswitch/pull/6234)) ([`e44eb13`](https://github.com/juspay/hyperswitch/commit/e44eb13c6188df4863dc6f960e35b2ab6e96c064)) + +### Documentation + +- Fix broken links to Running Additional Services ([#6243](https://github.com/juspay/hyperswitch/pull/6243)) ([`da6c0ff`](https://github.com/juspay/hyperswitch/commit/da6c0ff60bf059e73383ac37671c6df5c26d332c)) + +### Miscellaneous Tasks + +- V2 api changes for session token endpoint ([#6032](https://github.com/juspay/hyperswitch/pull/6032)) ([`6e355f3`](https://github.com/juspay/hyperswitch/commit/6e355f34a8cef41cb9d9047f7c8792d4b46c10d8)) + +### Build System / Dependencies + +- **docker-compose-development:** Address build failure of `hyperswitch-server` service ([#6217](https://github.com/juspay/hyperswitch/pull/6217)) ([`b79f75a`](https://github.com/juspay/hyperswitch/commit/b79f75a7ab9ed63a75defa2b3c5f9c170fca493e)) + +**Full Changelog:** [`2024.10.07.0...2024.10.08.0`](https://github.com/juspay/hyperswitch/compare/2024.10.07.0...2024.10.08.0) + +- - - + +## 2024.10.07.0 + +### Features + +- **opensearch:** Restrict search view access based on user roles and permissions ([#5932](https://github.com/juspay/hyperswitch/pull/5932)) ([`caa0693`](https://github.com/juspay/hyperswitch/commit/caa0693148764175201f7b1e2029fe29941cc7eb)) + +### Bug Fixes + +- Add `reference` in `sepa_bank_instructions` ([#6215](https://github.com/juspay/hyperswitch/pull/6215)) ([`036a2d5`](https://github.com/juspay/hyperswitch/commit/036a2d5056134c067ec76dfd2afce4855303f5d7)) +- Batch encrypt/decrypt on merchant connector account ([#6206](https://github.com/juspay/hyperswitch/pull/6206)) ([`b713948`](https://github.com/juspay/hyperswitch/commit/b7139483bb4735b7dfaf7e659ab33a16a90af1db)) + +### Refactors + +- **user_role:** Remove V1 insertion for `user_roles` and allow Invites for `org_admins` ([#6185](https://github.com/juspay/hyperswitch/pull/6185)) ([`c07ee28`](https://github.com/juspay/hyperswitch/commit/c07ee28c0a0b388ee8064a247e70484a3c4fec33)) +- **users:** Deprecate unused user APIs and stabilize v1 APIs ([#6114](https://github.com/juspay/hyperswitch/pull/6114)) ([`b2eb56e`](https://github.com/juspay/hyperswitch/commit/b2eb56e8d8589d1ae1a841a2c9e914c9d93e7993)) + +### Documentation + +- Change organization_id to id in organization endpoints ([#6218](https://github.com/juspay/hyperswitch/pull/6218)) ([`939483c`](https://github.com/juspay/hyperswitch/commit/939483cebe91d16266521827e9fbd654fb060ca6)) + +**Full Changelog:** [`2024.10.04.1...2024.10.07.0`](https://github.com/juspay/hyperswitch/compare/2024.10.04.1...2024.10.07.0) + +- - - + +## 2024.10.04.1 + +### Features + +- **connector:** Add dynamic duitnow qr code, google pay and applpe pay for fiuu ([#6204](https://github.com/juspay/hyperswitch/pull/6204)) ([`2e54186`](https://github.com/juspay/hyperswitch/commit/2e54186a809e1322683a9379923ce418d05d3619)) + +### Bug Fixes + +- **router:** Persist card_network if present for non co-badged cards ([#6212](https://github.com/juspay/hyperswitch/pull/6212)) ([`7564826`](https://github.com/juspay/hyperswitch/commit/75648262e7f741351c1149cd01083065d17bde7f)) + +**Full Changelog:** [`2024.10.04.0...2024.10.04.1`](https://github.com/juspay/hyperswitch/compare/2024.10.04.0...2024.10.04.1) + +- - - + +## 2024.10.04.0 + +### Features + +- **connector:** [Digital Virgo] template for integration ([#6145](https://github.com/juspay/hyperswitch/pull/6145)) ([`be3cf2c`](https://github.com/juspay/hyperswitch/commit/be3cf2c8693f4725e8c8ebd59412385dd4dcb7a1)) +- **router:** Add profile level auto retries config support ([#6200](https://github.com/juspay/hyperswitch/pull/6200)) ([`5648977`](https://github.com/juspay/hyperswitch/commit/56489771e403864602adff5f954d1f59c65764c3)) + +### Bug Fixes + +- **bug:** [IATAPAY] Fix PCM value for UPI_COLLECT ([#6207](https://github.com/juspay/hyperswitch/pull/6207)) ([`81e3d9d`](https://github.com/juspay/hyperswitch/commit/81e3d9df901d1b874dcbd5cd01f0b5532ae981a1)) +- **payment_intent:** Batch encrypt and decrypt payment intent ([#6164](https://github.com/juspay/hyperswitch/pull/6164)) ([`369939a`](https://github.com/juspay/hyperswitch/commit/369939a37385fe85fd3430d9be0b7b0698962625)) + +**Full Changelog:** [`2024.10.03.0...2024.10.04.0`](https://github.com/juspay/hyperswitch/compare/2024.10.03.0...2024.10.04.0) + +- - - + +## 2024.10.03.0 + +### Features + +- **connector:** [Nexixpay] add Payment & Refunds flows for cards ([#5864](https://github.com/juspay/hyperswitch/pull/5864)) ([`602f50b`](https://github.com/juspay/hyperswitch/commit/602f50b939f320ea9d85dff28dfe3f5c65afeb70)) + +**Full Changelog:** [`2024.10.02.0...2024.10.03.0`](https://github.com/juspay/hyperswitch/compare/2024.10.02.0...2024.10.03.0) + +- - - + +## 2024.10.02.0 + +### Refactors + +- **connector:** Move connector Dlocal and Square from router to hyperswitch_connector crate ([#6156](https://github.com/juspay/hyperswitch/pull/6156)) ([`0508025`](https://github.com/juspay/hyperswitch/commit/05080259132fb12cdef40a999bd02b6fe2beeeaa)) + +### Miscellaneous Tasks + +- Intoduce GenericError enum variant in enum ConnectorError ([#6143](https://github.com/juspay/hyperswitch/pull/6143)) ([`b694171`](https://github.com/juspay/hyperswitch/commit/b694171bab2f9d18d4e50bcc106da98ea5713297)) + +**Full Changelog:** [`2024.10.01.0...2024.10.02.0`](https://github.com/juspay/hyperswitch/compare/2024.10.01.0...2024.10.02.0) + +- - - + ## 2024.10.01.0 ### Features diff --git a/Cargo.lock b/Cargo.lock index d90c577152d0..e34c80bd9278 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -353,6 +353,7 @@ dependencies = [ "bigdecimal", "common_enums", "common_utils", + "currency_conversion", "diesel_models", "error-stack", "futures 0.3.30", @@ -363,6 +364,7 @@ dependencies = [ "opensearch", "reqwest 0.11.27", "router_env", + "rust_decimal", "serde", "serde_json", "sqlx", @@ -453,9 +455,11 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" name = "api_models" version = "0.1.0" dependencies = [ + "actix-multipart", "actix-web", "cards", "common_enums", + "common_types", "common_utils", "error-stack", "euclid", @@ -1426,34 +1430,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "axum" -version = "0.6.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" -dependencies = [ - "async-trait", - "axum-core 0.3.4", - "bitflags 1.3.2", - "bytes 1.7.1", - "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.30", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "rustversion", - "serde", - "sync_wrapper 0.1.2", - "tower", - "tower-layer", - "tower-service", -] - [[package]] name = "axum" version = "0.7.5" @@ -1461,7 +1437,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" dependencies = [ "async-trait", - "axum-core 0.4.3", + "axum-core", "bytes 1.7.1", "futures-util", "http 1.1.0", @@ -1481,23 +1457,6 @@ dependencies = [ "tower-service", ] -[[package]] -name = "axum-core" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" -dependencies = [ - "async-trait", - "bytes 1.7.1", - "futures-util", - "http 0.2.12", - "http-body 0.4.6", - "mime", - "rustversion", - "tower-layer", - "tower-service", -] - [[package]] name = "axum-core" version = "0.4.3" @@ -1846,6 +1805,8 @@ dependencies = [ "common_utils", "error-stack", "masking", + "once_cell", + "regex", "router_env", "serde", "serde_json", @@ -1884,9 +1845,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.1.18" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" dependencies = [ "jobserver", "libc", @@ -1959,6 +1920,16 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "chumsky" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" +dependencies = [ + "hashbrown 0.14.5", + "stacker", +] + [[package]] name = "ciborium" version = "0.2.2" @@ -2046,6 +2017,7 @@ name = "common_enums" version = "0.1.0" dependencies = [ "diesel", + "masking", "router_derive", "serde", "serde_json", @@ -2054,6 +2026,18 @@ dependencies = [ "utoipa", ] +[[package]] +name = "common_types" +version = "0.1.0" +dependencies = [ + "common_enums", + "common_utils", + "diesel", + "serde", + "serde_json", + "utoipa", +] + [[package]] name = "common_utils" version = "0.1.0" @@ -2764,6 +2748,7 @@ version = "0.1.0" dependencies = [ "async-bb8-diesel", "common_enums", + "common_types", "common_utils", "diesel", "error-stack", @@ -2957,6 +2942,22 @@ dependencies = [ "zeroize", ] +[[package]] +name = "email-encoding" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60d1d33cdaede7e24091f039632eb5d3c7469fe5b066a985281a34fc70fa317f" +dependencies = [ + "base64 0.22.1", + "memchr", +] + +[[package]] +name = "email_address" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" + [[package]] name = "encoding_rs" version = "0.8.34" @@ -3124,17 +3125,20 @@ dependencies = [ "dyn-clone", "error-stack", "hex", + "http-body-util", "hyper 0.14.30", "hyper-proxy", + "hyper-util", "hyperswitch_interfaces", + "lettre", "masking", "once_cell", - "prost 0.13.2", + "prost", "router_env", "serde", "thiserror", "tokio 1.40.0", - "tonic 0.12.2", + "tonic", "tonic-build", "tonic-reflection", "tonic-types", @@ -3692,6 +3696,12 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "histogram" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cb882ccb290b8646e554b157ab0b71e64e8d5bef775cd66b6531e52d302669" + [[package]] name = "hkdf" version = "0.12.4" @@ -3719,6 +3729,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "hostname" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "windows", +] + [[package]] name = "hsdev" version = "0.1.0" @@ -3901,18 +3922,6 @@ dependencies = [ "tokio-rustls 0.24.1", ] -[[package]] -name = "hyper-timeout" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" -dependencies = [ - "hyper 0.14.30", - "pin-project-lite", - "tokio 1.40.0", - "tokio-io-timeout", -] - [[package]] name = "hyper-timeout" version = "0.5.1" @@ -3957,9 +3966,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.7" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes 1.7.1", "futures-channel", @@ -3970,7 +3979,6 @@ dependencies = [ "pin-project-lite", "socket2", "tokio 1.40.0", - "tower", "tower-service", "tracing", ] @@ -3988,26 +3996,34 @@ dependencies = [ "cards", "common_enums", "common_utils", + "encoding_rs", "error-stack", "hex", "http 0.2.12", "hyperswitch_domain_models", "hyperswitch_interfaces", "image", + "lazy_static", "masking", + "mime", "once_cell", "qrcode", + "quick-xml", "rand", "regex", "reqwest 0.11.27", "ring 0.17.8", "router_env", + "roxmltree", "serde", "serde_json", + "serde_qs", "serde_urlencoded", + "serde_with", "strum 0.26.3", "time", "url", + "urlencoding", "uuid", ] @@ -4032,6 +4048,7 @@ dependencies = [ "async-trait", "cards", "common_enums", + "common_types", "common_utils", "diesel_models", "error-stack", @@ -4102,6 +4119,124 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec 1.13.2", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -4118,6 +4253,27 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec 1.13.2", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "ignore" version = "0.4.22" @@ -4301,6 +4457,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -4432,6 +4597,31 @@ dependencies = [ "spin 0.9.8", ] +[[package]] +name = "lettre" +version = "0.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0161e452348e399deb685ba05e55ee116cae9410f4f51fe42d597361444521d9" +dependencies = [ + "base64 0.22.1", + "chumsky", + "email-encoding", + "email_address", + "fastrand 2.1.1", + "futures-util", + "hostname", + "httpdate", + "idna 1.0.3", + "mime", + "native-tls", + "nom", + "percent-encoding", + "quoted_printable", + "socket2", + "tokio 1.40.0", + "url", +] + [[package]] name = "libc" version = "0.2.158" @@ -4507,6 +4697,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" + [[package]] name = "local-channel" version = "0.1.5" @@ -4558,6 +4754,15 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "lz4_flex" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5" +dependencies = [ + "twox-hash", +] + [[package]] name = "masking" version = "0.1.0" @@ -4565,6 +4770,7 @@ dependencies = [ "bytes 1.7.1", "diesel", "erased-serde 0.4.5", + "scylla", "serde", "serde_json", "subtle", @@ -4807,9 +5013,9 @@ checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" [[package]] name = "mutually_exclusive_features" -version = "0.0.3" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d02c0b00610773bb7fc61d85e13d86c7858cbdf00e1a120bfc41bc055dbaa0e" +checksum = "e94e1e6445d314f972ff7395df2de295fe51b71821694f0b0e1e79c4f12c8577" [[package]] name = "nanoid" @@ -5064,6 +5270,7 @@ name = "openapi" version = "0.1.0" dependencies = [ "api_models", + "common_types", "common_utils", "router_env", "serde_json", @@ -5172,76 +5379,72 @@ dependencies = [ [[package]] name = "opentelemetry" -version = "0.19.0" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab70038c28ed37b97d8ed414b6429d343a8bbf44c9f79ec854f3a643029ba6d7" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror", + "tracing", +] + +[[package]] +name = "opentelemetry-aws" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4b8347cc26099d3aeee044065ecc3ae11469796b4d65d065a23a584ed92a6f" +checksum = "85eacb6bb0b662955ba69d788c979462b079e70903e29867c2303cc1305ec8de" dependencies = [ - "opentelemetry_api", + "once_cell", + "opentelemetry", "opentelemetry_sdk", + "tracing", ] [[package]] name = "opentelemetry-otlp" -version = "0.12.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8af72d59a4484654ea8eb183fea5ae4eb6a41d7ac3e3bae5f4d2a282a3a7d3ca" +checksum = "91cf61a1868dacc576bf2b2a1c3e9ab150af7272909e80085c3173384fe11f76" dependencies = [ "async-trait", - "futures 0.3.30", - "futures-util", - "http 0.2.12", + "futures-core", + "http 1.1.0", "opentelemetry", "opentelemetry-proto", - "prost 0.11.9", + "opentelemetry_sdk", + "prost", "thiserror", "tokio 1.40.0", - "tonic 0.8.3", + "tonic", ] [[package]] name = "opentelemetry-proto" -version = "0.2.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "045f8eea8c0fa19f7d48e7bc3128a39c2e5c533d5c61298c548dfefc1064474c" +checksum = "a6e05acbfada5ec79023c85368af14abd0b307c015e9064d249b2a950ef459a6" dependencies = [ - "futures 0.3.30", - "futures-util", "opentelemetry", - "prost 0.11.9", - "tonic 0.8.3", + "opentelemetry_sdk", + "prost", + "tonic", ] [[package]] -name = "opentelemetry_api" -version = "0.19.0" +name = "opentelemetry_sdk" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed41783a5bf567688eb38372f2b7a8530f5a607a4b49d38dd7573236c23ca7e2" +checksum = "231e9d6ceef9b0b2546ddf52335785ce41252bc7474ee8ba05bfad277be13ab8" dependencies = [ - "fnv", - "futures-channel", - "futures-util", - "indexmap 1.9.3", - "once_cell", - "pin-project-lite", - "thiserror", - "urlencoding", -] - -[[package]] -name = "opentelemetry_sdk" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b3a2a91fdbfdd4d212c0dcc2ab540de2c2bcbbd90be17de7a7daf8822d010c1" -dependencies = [ - "async-trait", - "crossbeam-channel", - "dashmap", - "fnv", + "async-trait", "futures-channel", "futures-executor", "futures-util", - "once_cell", - "opentelemetry_api", + "glob", + "opentelemetry", "percent-encoding", "rand", "thiserror", @@ -5770,16 +5973,6 @@ dependencies = [ "unarray", ] -[[package]] -name = "prost" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" -dependencies = [ - "bytes 1.7.1", - "prost-derive 0.11.9", -] - [[package]] name = "prost" version = "0.13.2" @@ -5787,7 +5980,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b2ecbe40f08db5c006b5764a2645f7f3f141ce756412ac9e1dd6087e6d32995" dependencies = [ "bytes 1.7.1", - "prost-derive 0.13.2", + "prost-derive", ] [[package]] @@ -5798,32 +5991,19 @@ checksum = "f8650aabb6c35b860610e9cff5dc1af886c9e25073b7b1712a68972af4281302" dependencies = [ "bytes 1.7.1", "heck 0.5.0", - "itertools 0.12.1", + "itertools 0.13.0", "log", "multimap", "once_cell", "petgraph", "prettyplease", - "prost 0.13.2", + "prost", "prost-types", "regex", "syn 2.0.77", "tempfile", ] -[[package]] -name = "prost-derive" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" -dependencies = [ - "anyhow", - "itertools 0.10.5", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "prost-derive" version = "0.13.2" @@ -5831,7 +6011,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.13.0", "proc-macro2", "quote", "syn 2.0.77", @@ -5843,7 +6023,16 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60caa6738c7369b940c3d49246a8d1749323674c65cb13010134f5c9bad5b519" dependencies = [ - "prost 0.13.2", + "prost", +] + +[[package]] +name = "psm" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810" +dependencies = [ + "cc", ] [[package]] @@ -5915,6 +6104,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "quoted_printable" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73" + [[package]] name = "r2d2" version = "0.8.10" @@ -5962,6 +6157,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_pcg" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" +dependencies = [ + "rand_core", +] + [[package]] name = "rand_xorshift" version = "0.3.0" @@ -6358,6 +6562,7 @@ dependencies = [ "cards", "clap", "common_enums", + "common_types", "common_utils", "config", "cookie 0.18.1", @@ -6408,7 +6613,6 @@ dependencies = [ "ring 0.17.8", "router_derive", "router_env", - "roxmltree", "rust-i18n", "rust_decimal", "rustc-hash", @@ -6436,6 +6640,7 @@ dependencies = [ "unicode-segmentation", "unidecode", "url", + "urlencoding", "utoipa", "uuid", "validator", @@ -6470,7 +6675,9 @@ dependencies = [ "gethostname", "once_cell", "opentelemetry", + "opentelemetry-aws", "opentelemetry-otlp", + "opentelemetry_sdk", "rustc-hash", "serde", "serde_json", @@ -6882,6 +7089,65 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "scylla" +version = "0.15.0" +source = "git+https://github.com/juspay/scylla-rust-driver.git?rev=5700aa2847b25437cdd4fcf34d707aa90dca8b89#5700aa2847b25437cdd4fcf34d707aa90dca8b89" +dependencies = [ + "arc-swap", + "async-trait", + "byteorder", + "bytes 1.7.1", + "chrono", + "dashmap", + "futures 0.3.30", + "hashbrown 0.14.5", + "histogram", + "itertools 0.13.0", + "lazy_static", + "lz4_flex", + "rand", + "rand_pcg", + "scylla-cql", + "scylla-macros", + "smallvec 1.13.2", + "snap", + "socket2", + "thiserror", + "tokio 1.40.0", + "tracing", + "uuid", +] + +[[package]] +name = "scylla-cql" +version = "0.4.0" +source = "git+https://github.com/juspay/scylla-rust-driver.git?rev=5700aa2847b25437cdd4fcf34d707aa90dca8b89#5700aa2847b25437cdd4fcf34d707aa90dca8b89" +dependencies = [ + "async-trait", + "byteorder", + "bytes 1.7.1", + "lz4_flex", + "scylla-macros", + "snap", + "stable_deref_trait", + "thiserror", + "tokio 1.40.0", + "uuid", + "yoke", +] + +[[package]] +name = "scylla-macros" +version = "0.7.0" +source = "git+https://github.com/juspay/scylla-rust-driver.git?rev=5700aa2847b25437cdd4fcf34d707aa90dca8b89#5700aa2847b25437cdd4fcf34d707aa90dca8b89" +dependencies = [ + "darling 0.20.10", + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "sdd" version = "3.0.2" @@ -7305,6 +7571,12 @@ dependencies = [ "serde", ] +[[package]] +name = "snap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" + [[package]] name = "socket2" version = "0.5.7" @@ -7559,6 +7831,25 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "stacker" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799c883d55abdb5e98af1a7b3f23b9b6de8ecada0ecac058672d7635eb48ca7b" +dependencies = [ + "cc", + "cfg-if 1.0.0", + "libc", + "psm", + "windows-sys 0.59.0", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "storage_impl" version = "0.1.0" @@ -7976,6 +8267,16 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -8096,16 +8397,6 @@ dependencies = [ "log", ] -[[package]] -name = "tokio-io-timeout" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" -dependencies = [ - "pin-project-lite", - "tokio 1.40.0", -] - [[package]] name = "tokio-macros" version = "2.4.0" @@ -8180,9 +8471,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.15" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", @@ -8360,45 +8651,13 @@ dependencies = [ [[package]] name = "tonic" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb" -dependencies = [ - "async-stream", - "async-trait", - "axum 0.6.20", - "base64 0.13.1", - "bytes 1.7.1", - "futures-core", - "futures-util", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.30", - "hyper-timeout 0.4.1", - "percent-encoding", - "pin-project", - "prost 0.11.9", - "prost-derive 0.11.9", - "tokio 1.40.0", - "tokio-stream", - "tokio-util", - "tower", - "tower-layer", - "tower-service", - "tracing", - "tracing-futures", -] - -[[package]] -name = "tonic" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f6ba989e4b2c58ae83d862d3a3e27690b6e3ae630d0deb59f3697f32aa88ad" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" dependencies = [ "async-stream", "async-trait", - "axum 0.7.5", + "axum", "base64 0.22.1", "bytes 1.7.1", "h2 0.4.6", @@ -8406,11 +8665,11 @@ dependencies = [ "http-body 1.0.1", "http-body-util", "hyper 1.4.1", - "hyper-timeout 0.5.1", + "hyper-timeout", "hyper-util", "percent-encoding", "pin-project", - "prost 0.13.2", + "prost", "socket2", "tokio 1.40.0", "tokio-stream", @@ -8439,11 +8698,11 @@ version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b56b874eedb04f89907573b408eab1e87c1c1dce43aac6ad63742f57faa99ff" dependencies = [ - "prost 0.13.2", + "prost", "prost-types", "tokio 1.40.0", "tokio-stream", - "tonic 0.12.2", + "tonic", ] [[package]] @@ -8452,9 +8711,9 @@ version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d967793411bc1a5392accf4731114295f0fd122865d22cde46a8584b03402b2" dependencies = [ - "prost 0.13.2", + "prost", "prost-types", - "tonic 0.12.2", + "tonic", ] [[package]] @@ -8519,9 +8778,9 @@ dependencies = [ [[package]] name = "tracing-actix-web" -version = "0.7.11" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee9e39a66d9b615644893ffc1704d2a89b5b315b7fd0228ad3182ca9a306b19" +checksum = "54a9f5c1aca50ebebf074ee665b9f99f2e84906dcf6b993a0d0090edb835166d" dependencies = [ "actix-web", "mutually_exclusive_features", @@ -8576,17 +8835,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "tracing-log" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - [[package]] name = "tracing-log" version = "0.2.0" @@ -8600,16 +8848,20 @@ dependencies = [ [[package]] name = "tracing-opentelemetry" -version = "0.19.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00a39dcf9bfc1742fa4d6215253b33a6e474be78275884c216fc2a06267b3600" +checksum = "97a971f6058498b5c0f1affa23e7ea202057a7301dbff68e968b2d578bcbd053" dependencies = [ + "js-sys", "once_cell", "opentelemetry", + "opentelemetry_sdk", + "smallvec 1.13.2", "tracing", "tracing-core", - "tracing-log 0.1.4", + "tracing-log", "tracing-subscriber", + "web-time", ] [[package]] @@ -8639,7 +8891,7 @@ dependencies = [ "thread_local", "tracing", "tracing-core", - "tracing-log 0.2.0", + "tracing-log", "tracing-serde", ] @@ -8660,6 +8912,16 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if 1.0.0", + "static_assertions", +] + [[package]] name = "typeid" version = "1.0.2" @@ -8813,7 +9075,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", - "idna", + "idna 0.5.0", "percent-encoding", "serde", ] @@ -8830,6 +9092,18 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "110352d4e9076c67839003c7788d8604e24dcded13e0b375af3efaa8cf468517" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -8875,7 +9149,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da339118f018cc70ebf01fafc103360528aad53717e4bf311db929cb01cb9345" dependencies = [ - "idna", + "idna 0.5.0", "once_cell", "regex", "serde", @@ -9067,6 +9341,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webdriver" version = "0.46.0" @@ -9164,6 +9448,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -9403,6 +9697,18 @@ dependencies = [ "url", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "ws2_32-sys" version = "0.2.1" @@ -9454,6 +9760,30 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "yoke" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", + "synstructure 0.13.1", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -9475,12 +9805,55 @@ dependencies = [ "syn 2.0.77", ] +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", + "synstructure 0.13.1", +] + [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "zstd" version = "0.13.2" diff --git a/Cargo.toml b/Cargo.toml index 90eb996b82b3..dc60c64f667e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,12 +18,16 @@ unused_qualifications = "warn" [workspace.lints.clippy] as_conversions = "warn" +cloned_instead_of_copied = "warn" +dbg_macro = "warn" expect_used = "warn" +fn_params_excessive_bools = "warn" index_refutable_slice = "warn" indexing_slicing = "warn" large_futures = "warn" match_on_vec_items = "warn" missing_panics_doc = "warn" +mod_module_files = "warn" out_of_bounds_indexing = "warn" panic = "warn" panic_in_result_fn = "warn" @@ -31,11 +35,14 @@ panicking_unwrap = "warn" print_stderr = "warn" print_stdout = "warn" todo = "warn" +trivially_copy_pass_by_ref = "warn" unimplemented = "warn" +unnecessary_self_imports = "warn" unreachable = "warn" unwrap_in_result = "warn" unwrap_used = "warn" use_self = "warn" +wildcard_dependencies = "warn" # Lints to allow option_map_unit_fn = "allow" diff --git a/INSTALL_dependencies.sh b/INSTALL_dependencies.sh index 3ec36ccc66e0..e1d666b97a30 100755 --- a/INSTALL_dependencies.sh +++ b/INSTALL_dependencies.sh @@ -37,7 +37,7 @@ set -o nounset # utilities # convert semver to comparable integer -if [[ `id -u` -ne 0 ]]; then +if [[ "$(id -u)" -ne 0 ]]; then print_info "requires sudo" SUDO=sudo else @@ -45,10 +45,10 @@ else fi ver () { - printf "%03d%03d%03d%03d" `echo "$1" | tr '.' ' '`; + printf "%03d%03d%03d%03d" "$(echo "$1" | tr '.' ' ')"; } -PROGNAME=`basename $0` +PROGNAME="$(basename $0)" print_info () { echo -e "$PROGNAME: $*" } @@ -125,10 +125,10 @@ if command -v cargo > /dev/null; then need_cmd rustc - RUST_VERSION=`rustc -V | cut -d " " -f 2` + RUST_VERSION="$(rustc -V | cut -d " " -f 2)" - _HAVE_VERSION=`ver ${RUST_VERSION}` - _NEED_VERSION=`ver ${RUST_MSRV}` + _HAVE_VERSION="$(ver ${RUST_VERSION})" + _NEED_VERSION="$(ver ${RUST_MSRV})" print_info "Found rust version \"${RUST_VERSION}\". MSRV is \"${RUST_MSRV}\"" @@ -166,7 +166,7 @@ install_dep () { $INSTALL_CMD $* } -if [[ ! -x "`command -v psql`" ]] || [[ ! -x "`command -v redis-server`" ]] ; then +if [[ ! -x "$(command -v psql)" ]] || [[ ! -x "$(command -v redis-server)" ]] ; then print_info "Missing dependencies. Trying to install" # java has an apt which seems to mess up when we look for apt diff --git a/Makefile b/Makefile index e64f0c5bcb08..7614ff25ca54 100644 --- a/Makefile +++ b/Makefile @@ -84,7 +84,7 @@ clippy : # make euclid-wasm euclid-wasm: - wasm-pack build --target web --out-dir $(ROOT_DIR)/wasm --out-name euclid $(ROOT_DIR)/crates/euclid_wasm -- --features dummy_connector + wasm-pack build --target web --out-dir $(ROOT_DIR)/wasm --out-name euclid $(ROOT_DIR)/crates/euclid_wasm -- --features dummy_connector,v1 # Run Rust tests of project. # diff --git a/README.md b/README.md index 0428ff386946..10fdb6afc760 100644 --- a/README.md +++ b/README.md @@ -10,24 +10,19 @@ The single API to access payment ecosystems across 130+ countries

Try a Payment • - For Enterprises • - For ContributorsQuick SetupLocal Setup Guide (Hyperswitch App Server) • - Fast Integration for Stripe Users API Docs
- Supported Features • - Community • + Community and ContributionsBugs and feature requestsVersioning • - FAQsCopyright and License

- + @@ -49,55 +44,40 @@ The single API to access payment ecosystems across 130+ countries


- -Hyperswitch is a community-led, open payments switch to enable access to the best payments infrastructure for every digital business. +Hyperswitch is a community-led, open payments switch designed to empower digital businesses by providing fast, reliable, and affordable access to the best payments infrastructure. -Using Hyperswitch, you can: +Here are the components of Hyperswitch that deliver the whole solution: -- ⬇️ **Reduce dependency** on a single processor like Stripe or Braintree -- 🧑‍💻 **Reduce Dev effort** by 90% to add & maintain integrations -- 🚀 **Improve success rates** with seamless failover and auto-retries -- 💸 **Reduce processing fees** with smart routing -- 🎨 **Customize payment flows** with full visibility and control -- 🌐 **Increase business reach** with local/alternate payment methods +* [Hyperswitch Backend](https://github.com/juspay/hyperswitch): Powering Payment Processing -
-Hyperswitch-Product +* [SDK (Frontend)](https://github.com/juspay/hyperswitch-web): Simplifying Integration and Powering the UI -
-

⚡️ Try a Payment

-
+* [Control Centre](https://github.com/juspay/hyperswitch-control-center): Managing Operations with Ease -To quickly experience the ease that Hyperswitch provides while handling the payment, you can signup on [hyperswitch-control-center][dashboard-link], and try a payment. +Jump in and contribute to these repositories to help improve and expand Hyperswitch! + + -Congratulations 🎉 on making your first payment with Hyperswitch. - -

Get Started with Hyperswitch

+
+

⚡️ Quick Setup

-### [For Enterprises][docs-link-for-enterprise] - Hyperswitch helps enterprises in - - - Improving profitability - - Increasing conversion rates - - Lowering payment costs - - Streamlining payment operations - - Hyperswitch has ample features for businesses of all domains and sizes. [**Check out our offerings**][website-link]. +### Docker Compose + +You can run Hyperswitch on your system using Docker Compose after cloning this repository: -### [For Contributors][contributing-guidelines] - - Hyperswitch is an open-source project that aims to make digital payments accessible to people across the globe like a basic utility. With the vision of developing Hyperswitch as the **Linux of Payments**, we seek support from developers worldwide. +```shell +git clone --depth 1 --branch latest https://github.com/juspay/hyperswitch +cd hyperswitch +docker compose up -d +``` - Utilise the following resources to quickstart your journey with Hyperswitch - - - [Guide for contributors][contributing-guidelines] - - [Developer Docs][docs-link-for-developers] - - [Learning Resources][learning-resources] +This will start the app server, web client/SDK and control center. - -

⚡️ Quick Setup

-
+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]. ### One-click deployment on AWS cloud @@ -112,21 +92,6 @@ The fastest and easiest way to try Hyperswitch is via our CDK scripts 3. Follow the instructions provided on the console to successfully deploy Hyperswitch -### Run it on your system - -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 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]. - [docs-link-for-enterprise]: https://docs.hyperswitch.io/hyperswitch-cloud/quickstart [docs-link-for-developers]: https://docs.hyperswitch.io/hyperswitch-open-source/overview [contributing-guidelines]: docs/CONTRIBUTING.md @@ -134,78 +99,24 @@ setup, which includes the [scheduler and monitoring services][docker-compose-sch [website-link]: https://hyperswitch.io/ [learning-resources]: https://docs.hyperswitch.io/learn-more/payment-flows [local-setup-guide]: /docs/try_local_system.md -[docker-compose-scheduler-monitoring]: /docs/try_local_system.md#run-the-scheduler-and-monitoring-services - -

🔌 Fast Integration for Stripe Users

-
- -If you are already using Stripe, integrating with Hyperswitch is fun, fast & easy. -Try the steps below to get a feel for how quick the setup is: +[docker-compose-scheduler-monitoring]: /docs/try_local_system.md#running-additional-services -1. Get API keys from our [dashboard]. -2. Follow the instructions detailed on our - [documentation page][migrate-from-stripe]. - -[dashboard]: https://app.hyperswitch.io/register -[migrate-from-stripe]: https://hyperswitch.io/docs/migrateFromStripe - - -

✅ Supported Features

+
+

⚡️ Try a Payment

-### 🌟 Supported Payment Processors and Methods - -As of Aug 2024, Hyperswitch supports 50+ payment processors and multiple global payment methods. -In addition, we are continuously integrating new processors based on their reach and community requests. -Our target is to support 100+ processors by H2 2024. -You can find the latest list of payment processors, supported methods, and features [here][supported-connectors-and-features]. - -[supported-connectors-and-features]: https://hyperswitch.io/pm-list - -### 🌟 Hosted Version +To quickly experience the ease of Hyperswitch, sign up on the [Hyperswitch Control Center](https://app.hyperswitch.io/) and try a payment. Once you've completed your first transaction, you’ve successfully made your first payment with Hyperswitch! -In addition to all the features of the open-source product, our hosted version -provides features and support to manage your payment infrastructure, compliance, -analytics, and operations end-to-end: - -- **System Performance & Reliability** - - - Scalable to support 50000 tps - - System uptime of up to 99.99% - - Deployment with very low latency - - Hosting option with AWS or GCP - -- **Value Added Services** - - - Compliance Support, incl. PCI, GDPR, Card Vault etc - - Customise the integration or payment experience - - Control Center with elaborate analytics and reporting - - Integration with Risk Management Solutions - - Integration with other platforms like Subscription, E-commerce, Accounting, - etc. - -- **Enterprise Support** - - - 24x7 Email / On-call Support - - Dedicated Relationship Manager - - Custom dashboards with deep analytics, alerts, and reporting - - Expert team to consult and improve business metrics - -You can [try the hosted version in our sandbox][dashboard]. + +

✅ Community & Contributions

+
- +Join our Conversation in [Slack](https://join.slack.com/t/hyperswitch-io/shared_invite/zt-2jqxmpsbm-WXUENx022HjNEy~Ark7Orw), [Discord](https://discord.gg/wJZ7DVW8mm), [Twitter](https://x.com/hyperswitchio) -

💪 Join us in building Hyperswitch

@@ -237,61 +148,7 @@ reusable core and let companies build and customise it as per their specific req Security and Performance SLAs. 5. Maximise Value Creation: For developers, customers & partners. -### 🤍 Contributing - -This project is being created and maintained by [Juspay](https://juspay.in), -South Asia's largest payments orchestrator/switch, processing more than 50 -Million transactions per day. The solution has 1Mn+ lines of Haskell code built -over ten years. -Hyperswitch leverages our experience in building large-scale, enterprise-grade & -frictionless payment solutions. -It is built afresh for the global markets as an open-source product in Rust. -We are long-term committed to building and making it useful for the community. - -The product roadmap is open for the community's feedback. -We shall evolve a prioritisation process that is open and community-driven. -We welcome contributions from the community. Please read through our -[contributing guidelines](/docs/CONTRIBUTING.md). -Included are directions for opening issues, coding standards, and notes on -development. - -- We appreciate all types of contributions: code, documentation, demo creation, or some new way you want to contribute to us. - We will reward every contribution with a Hyperswitch branded t-shirt. -- 🦀 **Important note for Rust developers**: We aim for contributions from the community across a broad range of tracks. - Hence, we have prioritised simplicity and code readability over purely idiomatic code. - For example, some of the code in core functions (e.g., `payments_core`) is written to be more readable than pure-idiomatic. - -
-

👥 Community

-
- -Get updates on Hyperswitch development and chat with the community: - -- [Discord server][discord] for questions related to contributing to hyperswitch, questions about the architecture, components, etc. -- [Slack workspace][slack] for questions related to integrating hyperswitch, integrating a connector in hyperswitch, etc. -- [GitHub Discussions][github-discussions] to drop feature requests or suggest anything payments-related you need for your stack. - -[discord]: https://discord.gg/wJZ7DVW8mm -[slack]: https://join.slack.com/t/hyperswitch-io/shared_invite/zt-2awm23agh-p_G5xNpziv6yAiedTkkqLg -[github-discussions]: https://github.com/juspay/hyperswitch/discussions - -
-
- - Hyperswitch - Fast, reliable, and affordable open source payments switch | Product Hunt - -
-
- - Hyperswitch - Fast, reliable, and affordable open source payments switch | Product Hunt - -
-
- - Hyperswitch - Fast, reliable, and affordable open source payments switch | Product Hunt - -
-
+This project is being created and maintained by [Juspay](https://juspay.io)

🐞 Bugs and feature requests

@@ -309,15 +166,6 @@ If your problem or idea is not addressed yet, please [open a new issue]. Check the [CHANGELOG.md](./CHANGELOG.md) file for details. -
-

🤔 FAQs

-
- -Got more questions? -Please refer to our [FAQs page][faqs]. - -[faqs]: https://hyperswitch.io/docs/devSupport - diff --git a/add_connector.md b/add_connector.md index eda368db9c83..4d6e885b7edf 100644 --- a/add_connector.md +++ b/add_connector.md @@ -311,7 +311,7 @@ impl TryFrom> let payments_response_data = types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()), redirection_data, - mandate_reference: None, + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some( diff --git a/api-reference-v2/api-reference/api-key/api-key--create.mdx b/api-reference-v2/api-reference/api-key/api-key--create.mdx index a92a8ea77fd3..abc1dcda10f2 100644 --- a/api-reference-v2/api-reference/api-key/api-key--create.mdx +++ b/api-reference-v2/api-reference/api-key/api-key--create.mdx @@ -1,3 +1,3 @@ --- -openapi: post /v2/api_keys +openapi: post /v2/api-keys --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/api-key/api-key--list.mdx b/api-reference-v2/api-reference/api-key/api-key--list.mdx index 5975e9bd6cab..fb84b35fbc7c 100644 --- a/api-reference-v2/api-reference/api-key/api-key--list.mdx +++ b/api-reference-v2/api-reference/api-key/api-key--list.mdx @@ -1,3 +1,3 @@ --- -openapi: get /v2/api_keys/list +openapi: get /v2/api-keys/list --- diff --git a/api-reference-v2/api-reference/api-key/api-key--retrieve.mdx b/api-reference-v2/api-reference/api-key/api-key--retrieve.mdx index 13b87953f1b7..728643633576 100644 --- a/api-reference-v2/api-reference/api-key/api-key--retrieve.mdx +++ b/api-reference-v2/api-reference/api-key/api-key--retrieve.mdx @@ -1,3 +1,3 @@ --- -openapi: get /v2/api_keys/{key_id} +openapi: get /v2/api-keys/{id} --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/api-key/api-key--revoke.mdx b/api-reference-v2/api-reference/api-key/api-key--revoke.mdx index 37a9c9fcc092..b7ffd42e4493 100644 --- a/api-reference-v2/api-reference/api-key/api-key--revoke.mdx +++ b/api-reference-v2/api-reference/api-key/api-key--revoke.mdx @@ -1,3 +1,3 @@ --- -openapi: delete /v2/api_keys/{key_id} +openapi: delete /v2/api-keys/{id} --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/api-key/api-key--update.mdx b/api-reference-v2/api-reference/api-key/api-key--update.mdx index 8d1b6e2ee115..2434e4981fc6 100644 --- a/api-reference-v2/api-reference/api-key/api-key--update.mdx +++ b/api-reference-v2/api-reference/api-key/api-key--update.mdx @@ -1,3 +1,3 @@ --- -openapi: put /v2/api_keys/{key_id} +openapi: put /v2/api-keys/{id} --- diff --git a/api-reference-v2/api-reference/business-profile/merchant-connector--list.mdx b/api-reference-v2/api-reference/business-profile/merchant-connector--list.mdx index 6560f45e5fa2..93c5a980c27e 100644 --- a/api-reference-v2/api-reference/business-profile/merchant-connector--list.mdx +++ b/api-reference-v2/api-reference/business-profile/merchant-connector--list.mdx @@ -1,3 +1,3 @@ --- -openapi: get /v2/profiles/{profile_id}/connector_accounts +openapi: get /v2/profiles/{profile_id}/connector-accounts --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/business-profile/profile--connector-accounts-list.mdx b/api-reference-v2/api-reference/business-profile/profile--connector-accounts-list.mdx new file mode 100644 index 000000000000..55218be7c0b4 --- /dev/null +++ b/api-reference-v2/api-reference/business-profile/profile--connector-accounts-list.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /v2/profiles/{id}/connector-accounts +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/connector-account/connector-account--create.mdx b/api-reference-v2/api-reference/connector-account/connector-account--create.mdx new file mode 100644 index 000000000000..d672d6fa34dc --- /dev/null +++ b/api-reference-v2/api-reference/connector-account/connector-account--create.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /v2/connector-accounts +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/connector-account/connector-account--delete.mdx b/api-reference-v2/api-reference/connector-account/connector-account--delete.mdx new file mode 100644 index 000000000000..15fdd6644126 --- /dev/null +++ b/api-reference-v2/api-reference/connector-account/connector-account--delete.mdx @@ -0,0 +1,3 @@ +--- +openapi: delete /v2/connector-accounts/{id} +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/connector-account/connector-account--retrieve.mdx b/api-reference-v2/api-reference/connector-account/connector-account--retrieve.mdx new file mode 100644 index 000000000000..dbd26b9b10b1 --- /dev/null +++ b/api-reference-v2/api-reference/connector-account/connector-account--retrieve.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /v2/connector-accounts/{id} +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/connector-account/connector-account--update.mdx b/api-reference-v2/api-reference/connector-account/connector-account--update.mdx new file mode 100644 index 000000000000..fe864d538f8f --- /dev/null +++ b/api-reference-v2/api-reference/connector-account/connector-account--update.mdx @@ -0,0 +1,3 @@ +--- +openapi: put /v2/connector-accounts/{id} +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/customers/customers--create.mdx b/api-reference-v2/api-reference/customers/customers--create.mdx index c53a4cd62097..1d517ca26fc8 100644 --- a/api-reference-v2/api-reference/customers/customers--create.mdx +++ b/api-reference-v2/api-reference/customers/customers--create.mdx @@ -1,3 +1,3 @@ --- openapi: post /v2/customers ---- \ No newline at end of file +--- diff --git a/api-reference-v2/api-reference/customers/customers--delete.mdx b/api-reference-v2/api-reference/customers/customers--delete.mdx index eee5cffd5972..5d642f125f34 100644 --- a/api-reference-v2/api-reference/customers/customers--delete.mdx +++ b/api-reference-v2/api-reference/customers/customers--delete.mdx @@ -1,3 +1,3 @@ --- openapi: delete /v2/customers/{id} ---- \ No newline at end of file +--- diff --git a/api-reference-v2/api-reference/customers/customers--list.mdx b/api-reference-v2/api-reference/customers/customers--list.mdx index 432370e119a8..ae0d884e308e 100644 --- a/api-reference-v2/api-reference/customers/customers--list.mdx +++ b/api-reference-v2/api-reference/customers/customers--list.mdx @@ -1,3 +1,3 @@ --- openapi: get /v2/customers/list ---- \ No newline at end of file +--- diff --git a/api-reference-v2/api-reference/customers/customers--retrieve.mdx b/api-reference-v2/api-reference/customers/customers--retrieve.mdx index e89fe53d42a6..16f8a062553c 100644 --- a/api-reference-v2/api-reference/customers/customers--retrieve.mdx +++ b/api-reference-v2/api-reference/customers/customers--retrieve.mdx @@ -1,3 +1,3 @@ --- openapi: get /v2/customers/{id} ---- \ No newline at end of file +--- diff --git a/api-reference-v2/api-reference/customers/customers--update.mdx b/api-reference-v2/api-reference/customers/customers--update.mdx index 65433d8fad47..8d4bed86c97a 100644 --- a/api-reference-v2/api-reference/customers/customers--update.mdx +++ b/api-reference-v2/api-reference/customers/customers--update.mdx @@ -1,3 +1,3 @@ --- -openapi: post /v2/customers/{id} ---- \ No newline at end of file +openapi: put /v2/customers/{id} +--- diff --git a/api-reference-v2/api-reference/merchant-account/business-profile--list.mdx b/api-reference-v2/api-reference/merchant-account/business-profile--list.mdx index e14bc0d6ef34..069bd602ddf4 100644 --- a/api-reference-v2/api-reference/merchant-account/business-profile--list.mdx +++ b/api-reference-v2/api-reference/merchant-account/business-profile--list.mdx @@ -1,3 +1,3 @@ --- -openapi: get /v2/merchant_accounts/{account_id}/profiles +openapi: get /v2/merchant-accounts/{id}/profiles --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/merchant-account/merchant-account--create.mdx b/api-reference-v2/api-reference/merchant-account/merchant-account--create.mdx index d870b811aae8..38aed603f62e 100644 --- a/api-reference-v2/api-reference/merchant-account/merchant-account--create.mdx +++ b/api-reference-v2/api-reference/merchant-account/merchant-account--create.mdx @@ -1,3 +1,3 @@ --- -openapi: post /v2/merchant_accounts +openapi: post /v2/merchant-accounts --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/merchant-account/merchant-account--profile-list.mdx b/api-reference-v2/api-reference/merchant-account/merchant-account--profile-list.mdx new file mode 100644 index 000000000000..069bd602ddf4 --- /dev/null +++ b/api-reference-v2/api-reference/merchant-account/merchant-account--profile-list.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /v2/merchant-accounts/{id}/profiles +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/merchant-account/merchant-account--retrieve.mdx b/api-reference-v2/api-reference/merchant-account/merchant-account--retrieve.mdx index d082565234e8..3b744fb14063 100644 --- a/api-reference-v2/api-reference/merchant-account/merchant-account--retrieve.mdx +++ b/api-reference-v2/api-reference/merchant-account/merchant-account--retrieve.mdx @@ -1,3 +1,3 @@ --- -openapi: get /v2/merchant_accounts/{id} +openapi: get /v2/merchant-accounts/{id} --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/merchant-account/merchant-account--update.mdx b/api-reference-v2/api-reference/merchant-account/merchant-account--update.mdx index 51f80ceea302..eb2e92d0652f 100644 --- a/api-reference-v2/api-reference/merchant-account/merchant-account--update.mdx +++ b/api-reference-v2/api-reference/merchant-account/merchant-account--update.mdx @@ -1,3 +1,3 @@ --- -openapi: put /v2/merchant_accounts/{id} +openapi: put /v2/merchant-accounts/{id} --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/merchant-account/profile--list.mdx b/api-reference-v2/api-reference/merchant-account/profile--list.mdx deleted file mode 100644 index e14bc0d6ef34..000000000000 --- a/api-reference-v2/api-reference/merchant-account/profile--list.mdx +++ /dev/null @@ -1,3 +0,0 @@ ---- -openapi: get /v2/merchant_accounts/{account_id}/profiles ---- \ No newline at end of file diff --git a/api-reference-v2/api-reference/merchant-connector-account/connector-account--create.mdx b/api-reference-v2/api-reference/merchant-connector-account/connector-account--create.mdx new file mode 100644 index 000000000000..d672d6fa34dc --- /dev/null +++ b/api-reference-v2/api-reference/merchant-connector-account/connector-account--create.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /v2/connector-accounts +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/merchant-connector-account/connector-account--retrieve.mdx b/api-reference-v2/api-reference/merchant-connector-account/connector-account--retrieve.mdx new file mode 100644 index 000000000000..dbd26b9b10b1 --- /dev/null +++ b/api-reference-v2/api-reference/merchant-connector-account/connector-account--retrieve.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /v2/connector-accounts/{id} +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/merchant-connector-account/connector-account--update.mdx b/api-reference-v2/api-reference/merchant-connector-account/connector-account--update.mdx new file mode 100644 index 000000000000..fe864d538f8f --- /dev/null +++ b/api-reference-v2/api-reference/merchant-connector-account/connector-account--update.mdx @@ -0,0 +1,3 @@ +--- +openapi: put /v2/connector-accounts/{id} +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/merchant-connector-account/merchant-connector--create.mdx b/api-reference-v2/api-reference/merchant-connector-account/merchant-connector--create.mdx deleted file mode 100644 index d8cac2bab392..000000000000 --- a/api-reference-v2/api-reference/merchant-connector-account/merchant-connector--create.mdx +++ /dev/null @@ -1,3 +0,0 @@ ---- -openapi: post /v2/connector_accounts ---- \ No newline at end of file diff --git a/api-reference-v2/api-reference/merchant-connector-account/merchant-connector--delete.mdx b/api-reference-v2/api-reference/merchant-connector-account/merchant-connector--delete.mdx index 5c959648fffc..15fdd6644126 100644 --- a/api-reference-v2/api-reference/merchant-connector-account/merchant-connector--delete.mdx +++ b/api-reference-v2/api-reference/merchant-connector-account/merchant-connector--delete.mdx @@ -1,3 +1,3 @@ --- -openapi: delete /v2/connector_accounts/{id} +openapi: delete /v2/connector-accounts/{id} --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/merchant-connector-account/merchant-connector--retrieve.mdx b/api-reference-v2/api-reference/merchant-connector-account/merchant-connector--retrieve.mdx deleted file mode 100644 index 918de0312769..000000000000 --- a/api-reference-v2/api-reference/merchant-connector-account/merchant-connector--retrieve.mdx +++ /dev/null @@ -1,3 +0,0 @@ ---- -openapi: get /v2/connector_accounts/{id} ---- \ No newline at end of file diff --git a/api-reference-v2/api-reference/merchant-connector-account/merchant-connector--update.mdx b/api-reference-v2/api-reference/merchant-connector-account/merchant-connector--update.mdx deleted file mode 100644 index 6ccd052fb9bf..000000000000 --- a/api-reference-v2/api-reference/merchant-connector-account/merchant-connector--update.mdx +++ /dev/null @@ -1,3 +0,0 @@ ---- -openapi: put /v2/connector_accounts/{id} ---- \ No newline at end of file diff --git a/api-reference-v2/api-reference/organization/organization--merchant-account--list.mdx b/api-reference-v2/api-reference/organization/organization--merchant-account--list.mdx index 4057e7e18e5d..9a03e8713d12 100644 --- a/api-reference-v2/api-reference/organization/organization--merchant-account--list.mdx +++ b/api-reference-v2/api-reference/organization/organization--merchant-account--list.mdx @@ -1,3 +1,3 @@ --- -openapi: get /v2/organization/{organization_id}/merchant_accounts +openapi: get /v2/organization/{id}/merchant-accounts --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/organization/organization--retrieve.mdx b/api-reference-v2/api-reference/organization/organization--retrieve.mdx index 5cd3175b04f7..ba06f4cfb66b 100644 --- a/api-reference-v2/api-reference/organization/organization--retrieve.mdx +++ b/api-reference-v2/api-reference/organization/organization--retrieve.mdx @@ -1,3 +1,3 @@ --- -openapi: get /v2/organization/{organization_id} +openapi: get /v2/organization/{id} --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/organization/organization--update.mdx b/api-reference-v2/api-reference/organization/organization--update.mdx index 1ac924b71bcb..c365eb093f76 100644 --- a/api-reference-v2/api-reference/organization/organization--update.mdx +++ b/api-reference-v2/api-reference/organization/organization--update.mdx @@ -1,3 +1,3 @@ --- -openapi: put /v2/organization/{organization_id} +openapi: put /v2/organization/{id} --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/payment-methods/list-customer-saved-payment-methods-for-a-payment.mdx b/api-reference-v2/api-reference/payment-methods/list-customer-saved-payment-methods-for-a-payment.mdx new file mode 100644 index 000000000000..7809830b820b --- /dev/null +++ b/api-reference-v2/api-reference/payment-methods/list-customer-saved-payment-methods-for-a-payment.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /v2/payments/{id}/saved-payment-methods +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/payment-methods/list-payment-methods-for-a-customer.mdx b/api-reference-v2/api-reference/payment-methods/list-payment-methods-for-a-customer.mdx new file mode 100644 index 000000000000..ef5a27f9604d --- /dev/null +++ b/api-reference-v2/api-reference/payment-methods/list-payment-methods-for-a-customer.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /v2/customers/{id}/saved-payment-methods +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/payment-methods/list-saved-payment-methods-for-a-customer.mdx b/api-reference-v2/api-reference/payment-methods/list-saved-payment-methods-for-a-customer.mdx new file mode 100644 index 000000000000..ef5a27f9604d --- /dev/null +++ b/api-reference-v2/api-reference/payment-methods/list-saved-payment-methods-for-a-customer.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /v2/customers/{id}/saved-payment-methods +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/payment-methods/payment-method--confirm-intent.mdx b/api-reference-v2/api-reference/payment-methods/payment-method--confirm-intent.mdx new file mode 100644 index 000000000000..134374a7b6c1 --- /dev/null +++ b/api-reference-v2/api-reference/payment-methods/payment-method--confirm-intent.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /v2/payment-methods/{id}/confirm-intent +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/payment-methods/payment-method--create-intent.mdx b/api-reference-v2/api-reference/payment-methods/payment-method--create-intent.mdx new file mode 100644 index 000000000000..42cf716f2ab9 --- /dev/null +++ b/api-reference-v2/api-reference/payment-methods/payment-method--create-intent.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /v2/payment-methods/create-intent +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/payment-methods/payment-method--create.mdx b/api-reference-v2/api-reference/payment-methods/payment-method--create.mdx new file mode 100644 index 000000000000..1dce5179a94d --- /dev/null +++ b/api-reference-v2/api-reference/payment-methods/payment-method--create.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /v2/payment-methods +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/payment-methods/payment-method--delete.mdx b/api-reference-v2/api-reference/payment-methods/payment-method--delete.mdx new file mode 100644 index 000000000000..210bf843f97e --- /dev/null +++ b/api-reference-v2/api-reference/payment-methods/payment-method--delete.mdx @@ -0,0 +1,3 @@ +--- +openapi: delete /v2/payment-methods/{id} +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/payment-methods/payment-method--retrieve.mdx b/api-reference-v2/api-reference/payment-methods/payment-method--retrieve.mdx new file mode 100644 index 000000000000..957d9760b3f3 --- /dev/null +++ b/api-reference-v2/api-reference/payment-methods/payment-method--retrieve.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /v2/payment-methods/{id} +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/payment-methods/payment-method--update.mdx b/api-reference-v2/api-reference/payment-methods/payment-method--update.mdx new file mode 100644 index 000000000000..0adee195a6fa --- /dev/null +++ b/api-reference-v2/api-reference/payment-methods/payment-method--update.mdx @@ -0,0 +1,3 @@ +--- +openapi: patch /v2/payment-methods/{id}/update-saved-payment-method +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/payments/payments--confirm-intent.mdx b/api-reference-v2/api-reference/payments/payments--confirm-intent.mdx new file mode 100644 index 000000000000..58624c2771b9 --- /dev/null +++ b/api-reference-v2/api-reference/payments/payments--confirm-intent.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /v2/payments/{id}/confirm-intent +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/payments/payments--get-intent.mdx b/api-reference-v2/api-reference/payments/payments--get-intent.mdx new file mode 100644 index 000000000000..cd1321be217e --- /dev/null +++ b/api-reference-v2/api-reference/payments/payments--get-intent.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /v2/payments/{id}/get-intent +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/payments/payments--get.mdx b/api-reference-v2/api-reference/payments/payments--get.mdx new file mode 100644 index 000000000000..7b5f7f68dae8 --- /dev/null +++ b/api-reference-v2/api-reference/payments/payments--get.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /v2/payments/{id} +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/payments/payments--payment-methods-list.mdx b/api-reference-v2/api-reference/payments/payments--payment-methods-list.mdx new file mode 100644 index 000000000000..87e2a93586cc --- /dev/null +++ b/api-reference-v2/api-reference/payments/payments--payment-methods-list.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /v2/payments/{id}/payment-methods +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/payments/payments--session-token.mdx b/api-reference-v2/api-reference/payments/payments--session-token.mdx new file mode 100644 index 000000000000..615bafcc03d5 --- /dev/null +++ b/api-reference-v2/api-reference/payments/payments--session-token.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /v2/payments/{payment_id}/create-external-sdk-tokens +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/payments/payments--update-intent.mdx b/api-reference-v2/api-reference/payments/payments--update-intent.mdx new file mode 100644 index 000000000000..3ef58f4db721 --- /dev/null +++ b/api-reference-v2/api-reference/payments/payments--update-intent.mdx @@ -0,0 +1,3 @@ +--- +openapi: put /v2/payments/{id}/update-intent +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/profile/merchant-connector--list.mdx b/api-reference-v2/api-reference/profile/merchant-connector--list.mdx index 6560f45e5fa2..55218be7c0b4 100644 --- a/api-reference-v2/api-reference/profile/merchant-connector--list.mdx +++ b/api-reference-v2/api-reference/profile/merchant-connector--list.mdx @@ -1,3 +1,3 @@ --- -openapi: get /v2/profiles/{profile_id}/connector_accounts +openapi: get /v2/profiles/{id}/connector-accounts --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/profile/profile--activate-routing-algorithm.mdx b/api-reference-v2/api-reference/profile/profile--activate-routing-algorithm.mdx index 9ff6f4fdd578..ea9ee7596a02 100644 --- a/api-reference-v2/api-reference/profile/profile--activate-routing-algorithm.mdx +++ b/api-reference-v2/api-reference/profile/profile--activate-routing-algorithm.mdx @@ -1,3 +1,3 @@ --- -openapi: patch /v2/profiles/{profile_id}/activate_routing_algorithm +openapi: patch /v2/profiles/{id}/activate-routing-algorithm --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/profile/profile--deactivate-routing-algorithm.mdx b/api-reference-v2/api-reference/profile/profile--deactivate-routing-algorithm.mdx index 5cc815612e03..4d6b2d620c66 100644 --- a/api-reference-v2/api-reference/profile/profile--deactivate-routing-algorithm.mdx +++ b/api-reference-v2/api-reference/profile/profile--deactivate-routing-algorithm.mdx @@ -1,3 +1,3 @@ --- -openapi: patch /v2/profiles/{profile_id}/deactivate_routing_algorithm +openapi: patch /v2/profiles/{id}/deactivate-routing-algorithm --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/profile/profile--retrieve-active-routing-algorithm.mdx b/api-reference-v2/api-reference/profile/profile--retrieve-active-routing-algorithm.mdx index 7aba27485eda..143837676c22 100644 --- a/api-reference-v2/api-reference/profile/profile--retrieve-active-routing-algorithm.mdx +++ b/api-reference-v2/api-reference/profile/profile--retrieve-active-routing-algorithm.mdx @@ -1,3 +1,3 @@ --- -openapi: get /v2/profiles/{profile_id}/routing_algorithm +openapi: get /v2/profiles/{id}/routing-algorithm --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/profile/profile--retrieve-default-fallback-routing-algorithm.mdx b/api-reference-v2/api-reference/profile/profile--retrieve-default-fallback-routing-algorithm.mdx index 9b1b94290777..ebaad7c53ae3 100644 --- a/api-reference-v2/api-reference/profile/profile--retrieve-default-fallback-routing-algorithm.mdx +++ b/api-reference-v2/api-reference/profile/profile--retrieve-default-fallback-routing-algorithm.mdx @@ -1,3 +1,3 @@ --- -openapi: get /v2/profiles/{profile_id}/fallback_routing +openapi: get /v2/profiles/{id}/fallback-routing --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/profile/profile--retrieve.mdx b/api-reference-v2/api-reference/profile/profile--retrieve.mdx index 235bb7d7f502..28ca1fbd1b18 100644 --- a/api-reference-v2/api-reference/profile/profile--retrieve.mdx +++ b/api-reference-v2/api-reference/profile/profile--retrieve.mdx @@ -1,3 +1,3 @@ --- -openapi: get /v2/profiles/{profile_id} +openapi: get /v2/profiles/{id} --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/profile/profile--update-default-fallback-routing-algorithm.mdx b/api-reference-v2/api-reference/profile/profile--update-default-fallback-routing-algorithm.mdx index 0ba69796a7e9..b5df6a57ef8a 100644 --- a/api-reference-v2/api-reference/profile/profile--update-default-fallback-routing-algorithm.mdx +++ b/api-reference-v2/api-reference/profile/profile--update-default-fallback-routing-algorithm.mdx @@ -1,3 +1,3 @@ --- -openapi: patch /v2/profiles/{profile_id}/fallback_routing +openapi: patch /v2/profiles/{id}/fallback-routing --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/profile/profile--update.mdx b/api-reference-v2/api-reference/profile/profile--update.mdx index bf35d0afb64a..21b50001c63e 100644 --- a/api-reference-v2/api-reference/profile/profile--update.mdx +++ b/api-reference-v2/api-reference/profile/profile--update.mdx @@ -1,3 +1,3 @@ --- -openapi: put /v2/profiles/{profile_id} +openapi: put /v2/profiles/{id} --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/refunds/refunds--create.mdx b/api-reference-v2/api-reference/refunds/refunds--create.mdx new file mode 100644 index 000000000000..fa52f18c2867 --- /dev/null +++ b/api-reference-v2/api-reference/refunds/refunds--create.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /v2/refunds +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/routing/routing--create.mdx b/api-reference-v2/api-reference/routing/routing--create.mdx index 65ef15008f24..438abd8e2316 100644 --- a/api-reference-v2/api-reference/routing/routing--create.mdx +++ b/api-reference-v2/api-reference/routing/routing--create.mdx @@ -1,3 +1,3 @@ --- -openapi: post /v2/routing_algorithm +openapi: post /v2/routing-algorithm --- \ No newline at end of file diff --git a/api-reference-v2/api-reference/routing/routing--retrieve.mdx b/api-reference-v2/api-reference/routing/routing--retrieve.mdx index 03f209951c03..10db0200e18d 100644 --- a/api-reference-v2/api-reference/routing/routing--retrieve.mdx +++ b/api-reference-v2/api-reference/routing/routing--retrieve.mdx @@ -1,3 +1,3 @@ --- -openapi: get /v2/routing_algorithm/{routing_algorithm_id} +openapi: get /v2/routing-algorithm/{id} --- \ No newline at end of file diff --git a/api-reference-v2/essentials/webhooks.mdx b/api-reference-v2/essentials/webhooks.mdx deleted file mode 100644 index 20ce3518ea72..000000000000 --- a/api-reference-v2/essentials/webhooks.mdx +++ /dev/null @@ -1,45 +0,0 @@ -# Webhooks - -Webhooks are HTTP-based real-time push notifications that Hyperswitch would use for instant status communication to your server. Webhooks are vital in payments for the following reasons: - -- Preventing merchants from losing business due to delayed status communication (say, in case of flight or movie reservations where there is a need for instant payment confirmation). -- Prevent payment reconciliation issues where payments change from “Failed” to “Succeeded”. -- Providing the best payment experience for the end-user by instantly communicating payment status and fulfilling the purchase. - -## Configuring Webhooks - -You would need to set up a dedicated HTTPS or HTTP endpoint on your server with a URL as a webhook listener that will receive push notifications in the form of a POST request with JSON payload from the Hyperswitch server - Update the above endpoint on your Hyperswitch dashboard under Settings -> Webhooks -In order for Hyperswitch to receive updates from the connectors you have selected, you would need to update Hyperswitch’s corresponding endpoints on your respective connector dashboard instead of your webhook endpoints - - -Hyperswitch’s webhook endpoint format is as follows: - -| Environment | Webhook Endpoint | -| ----------- | ---------------- | -| Sandbox | sandbox.hyperswitch.io/webhooks/`{merchant_id}`/`{connector_name}` | -| Production | api.hyperswitch.io/webhooks/`{merchant_id}`/`{connector_name}`| - -## Handling Webhooks - -- **Select the events for Webhooks:** On the same page on the dashboard, select the events for which you would like to receive notifications. Currently, Webhooks are available on Hyperswitch for the following events: - - 1. payment_succeeded - 2. payment_failed - 3. payment_processing - 4. action_required - 5. refund_succeeded - 6. refund_failed - 7. dispute_opened - 8. dispute_expired - 9. dispute_accepted - 10. dispute_cancelled - 11. dispute_challenged - 12. dispute_won - 13. dispute_lost - -Click [**here**](https://juspay-78.mintlify.app/api-reference/schemas/outgoing--webhook) to see the webhook payload your endpoint would need to parse for each of the above events - -- **Return a 2xx response:** Your server must return a successful 2xx response on successful receipt of webhooks. - -- **Retries:** In case of 3xx, 4xx, or 5xx response or no response from your endpoint for webhooks, Hyperswitch has a retry mechanism that tries sending the webhooks again up to 3 times before marking the event as failed. diff --git a/api-reference-v2/logo/dark.svg b/api-reference-v2/logo/dark.svg index fbf0d89d4106..f07be0cea141 100644 --- a/api-reference-v2/logo/dark.svg +++ b/api-reference-v2/logo/dark.svg @@ -1,29 +1,21 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/api-reference-v2/logo/light.svg b/api-reference-v2/logo/light.svg index c951a909dd49..66b2c279d06f 100644 --- a/api-reference-v2/logo/light.svg +++ b/api-reference-v2/logo/light.svg @@ -1,29 +1,21 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/api-reference-v2/mint.json b/api-reference-v2/mint.json index 64047f5b5660..080adae5ad1c 100644 --- a/api-reference-v2/mint.json +++ b/api-reference-v2/mint.json @@ -28,7 +28,6 @@ { "group": "Essentials", "pages": [ - "essentials/webhooks", "essentials/error_codes", "essentials/rate_limit", "essentials/go-live" @@ -37,7 +36,26 @@ { "group": "Payments", "pages": [ - "api-reference/payments/payments--create-intent" + "api-reference/payments/payments--create-intent", + "api-reference/payments/payments--get-intent", + "api-reference/payments/payments--update-intent", + "api-reference/payments/payments--session-token", + "api-reference/payments/payments--payment-methods-list", + "api-reference/payments/payments--confirm-intent", + "api-reference/payments/payments--get" + ] + }, + { + "group": "Payment Methods", + "pages": [ + "api-reference/payment-methods/payment-method--create", + "api-reference/payment-methods/payment-method--retrieve", + "api-reference/payment-methods/payment-method--update", + "api-reference/payment-methods/payment-method--delete", + "api-reference/payment-methods/payment-method--create-intent", + "api-reference/payment-methods/payment-method--confirm-intent", + "api-reference/payment-methods/list-customer-saved-payment-methods-for-a-payment", + "api-reference/payment-methods/list-payment-methods-for-a-customer" ] }, { @@ -73,11 +91,11 @@ ] }, { - "group": "Merchant Connector Account", + "group": "Connector Account", "pages": [ - "api-reference/merchant-connector-account/merchant-connector--create", - "api-reference/merchant-connector-account/merchant-connector--retrieve", - "api-reference/merchant-connector-account/merchant-connector--update" + "api-reference/connector-account/connector-account--create", + "api-reference/connector-account/connector-account--retrieve", + "api-reference/connector-account/connector-account--update" ] }, { @@ -106,6 +124,10 @@ "api-reference/customers/customers--delete", "api-reference/customers/customers--list" ] + }, + { + "group": "Refunds", + "pages": ["api-reference/refunds/refunds--create"] } ], "footerSocials": { diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 17b44a98100f..636723d808a4 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -67,7 +67,7 @@ ] } }, - "/v2/organization/{organization_id}": { + "/v2/organization/{id}": { "get": { "tags": [ "Organization" @@ -77,7 +77,7 @@ "operationId": "Retrieve an Organization", "parameters": [ { - "name": "organization_id", + "name": "id", "in": "path", "description": "The unique identifier for the Organization", "required": true, @@ -116,7 +116,7 @@ "operationId": "Update an Organization", "parameters": [ { - "name": "organization_id", + "name": "id", "in": "path", "description": "The unique identifier for the Organization", "required": true, @@ -164,7 +164,7 @@ ] } }, - "/v2/organization/{organization_id}/merchant_accounts": { + "/v2/organization/{id}/merchant-accounts": { "get": { "tags": [ "Organization" @@ -174,7 +174,7 @@ "operationId": "List Merchant Accounts", "parameters": [ { - "name": "organization_id", + "name": "id", "in": "path", "description": "The unique identifier for the Organization", "required": true, @@ -208,13 +208,13 @@ ] } }, - "/v2/connector_accounts": { + "/v2/connector-accounts": { "post": { "tags": [ "Merchant Connector Account" ], - "summary": "Merchant Connector - Create", - "description": "Creates a new Merchant Connector for the merchant account. The connector could be a payment processor/facilitator/acquirer or a provider of specialized services like Fraud/Accounting etc.", + "summary": "Connector Account - Create", + "description": "Creates a new Connector Account for the merchant account. The connector could be a payment processor/facilitator/acquirer or a provider of specialized services like Fraud/Accounting etc.", "operationId": "Create a Merchant Connector", "requestBody": { "content": { @@ -285,12 +285,12 @@ ] } }, - "/v2/connector_accounts/{id}": { + "/v2/connector-accounts/{id}": { "get": { "tags": [ "Merchant Connector Account" ], - "summary": "Merchant Connector - Retrieve", + "summary": "Connector Account - Retrieve", "description": "Retrieves details of a Connector account", "operationId": "Retrieve a Merchant Connector", "parameters": [ @@ -333,8 +333,8 @@ "tags": [ "Merchant Connector Account" ], - "summary": "Merchant Connector - Update", - "description": "To update an existing Merchant Connector account. Helpful in enabling/disabling different payment methods and other settings for the connector", + "summary": "Connector Account - Update", + "description": "To update an existing Connector account. Helpful in enabling/disabling different payment methods and other settings for the connector", "operationId": "Update a Merchant Connector", "parameters": [ { @@ -445,7 +445,7 @@ ] } }, - "/v2/merchant_accounts": { + "/v2/merchant-accounts": { "post": { "tags": [ "Merchant Account" @@ -453,6 +453,20 @@ "summary": "Merchant Account - Create", "description": "Create a new account for a *merchant* and the *merchant* could be a seller or retailer or client who likes to receive and send payments.\n\nBefore creating the merchant account, it is mandatory to create an organization.", "operationId": "Create a Merchant Account", + "parameters": [ + { + "name": "X-Organization-Id", + "in": "header", + "description": "Organization ID for which the merchant account has to be created.", + "required": true, + "schema": { + "type": "string" + }, + "example": { + "X-Organization-Id": "org_abcdefghijklmnop" + } + } + ], "requestBody": { "content": { "application/json": { @@ -466,8 +480,7 @@ "primary_contact_person": "John Doe", "primary_email": "example@company.com" }, - "merchant_name": "Cloth Store", - "organization_id": "org_abcdefghijklmnop" + "merchant_name": "Cloth Store" } }, "Create a merchant account with metadata": { @@ -476,14 +489,12 @@ "metadata": { "key_1": "John Doe", "key_2": "Trends" - }, - "organization_id": "org_abcdefghijklmnop" + } } }, "Create a merchant account with minimal fields": { "value": { - "merchant_name": "Cloth Store", - "organization_id": "org_abcdefghijklmnop" + "merchant_name": "Cloth Store" } } } @@ -513,7 +524,7 @@ ] } }, - "/v2/merchant_accounts/{id}": { + "/v2/merchant-accounts/{id}": { "get": { "tags": [ "Merchant Account" @@ -562,7 +573,7 @@ "operationId": "Update a Merchant Account", "parameters": [ { - "name": "account_id", + "name": "id", "in": "path", "description": "The unique identifier for the merchant account", "required": true, @@ -619,17 +630,17 @@ ] } }, - "/v2/merchant_accounts/{account_id}/profiles": { + "/v2/merchant-accounts/{id}/profiles": { "get": { "tags": [ "Merchant Account" ], - "summary": "Profile - List", + "summary": "Merchant Account - Profile List", "description": "List profiles for an Merchant", "operationId": "List Profiles", "parameters": [ { - "name": "account_id", + "name": "id", "in": "path", "description": "The unique identifier for the Merchant", "required": true, @@ -671,6 +682,20 @@ "summary": "Profile - Create", "description": "Creates a new *profile* for a merchant", "operationId": "Create A Profile", + "parameters": [ + { + "name": "X-Merchant-Id", + "in": "header", + "description": "Merchant ID of the profile.", + "required": true, + "schema": { + "type": "string" + }, + "example": { + "X-Merchant-Id": "abc_iG5VNjsN9xuCg7Xx0uWh" + } + } + ], "requestBody": { "content": { "application/json": { @@ -710,7 +735,7 @@ ] } }, - "/v2/profiles/{profile_id}": { + "/v2/profiles/{id}": { "get": { "tags": [ "Profile" @@ -720,13 +745,25 @@ "operationId": "Retrieve a Profile", "parameters": [ { - "name": "profile_id", + "name": "id", "in": "path", "description": "The unique identifier for the profile", "required": true, "schema": { "type": "string" } + }, + { + "name": "X-Merchant-Id", + "in": "header", + "description": "Merchant ID of the profile.", + "required": true, + "schema": { + "type": "string" + }, + "example": { + "X-Merchant-Id": "abc_iG5VNjsN9xuCg7Xx0uWh" + } } ], "responses": { @@ -759,13 +796,25 @@ "operationId": "Update a Profile", "parameters": [ { - "name": "profile_id", + "name": "id", "in": "path", "description": "The unique identifier for the profile", "required": true, "schema": { "type": "string" } + }, + { + "name": "X-Merchant-Id", + "in": "header", + "description": "Merchant ID of the profile.", + "required": true, + "schema": { + "type": "string" + }, + "example": { + "X-Merchant-Id": "abc_iG5VNjsN9xuCg7Xx0uWh" + } } ], "requestBody": { @@ -807,23 +856,35 @@ ] } }, - "/v2/profiles/{profile_id}/connector_accounts": { + "/v2/profiles/{id}/connector-accounts": { "get": { "tags": [ "Business Profile" ], - "summary": "Merchant Connector - List", - "description": "List Merchant Connector Details for the business profile", + "summary": "Profile - Connector Accounts List", + "description": "List Connector Accounts for the profile", "operationId": "List all Merchant Connectors", "parameters": [ { - "name": "profile_id", + "name": "id", "in": "path", "description": "The unique identifier for the business profile", "required": true, "schema": { "type": "string" } + }, + { + "name": "X-Merchant-Id", + "in": "header", + "description": "Merchant ID of the profile.", + "required": true, + "schema": { + "type": "string" + }, + "example": { + "X-Merchant-Id": "abc_iG5VNjsN9xuCg7Xx0uWh" + } } ], "responses": { @@ -854,7 +915,7 @@ ] } }, - "/v2/profiles/{profile_id}/activate_routing_algorithm": { + "/v2/profiles/{id}/activate-routing-algorithm": { "patch": { "tags": [ "Profile" @@ -864,7 +925,7 @@ "operationId": "Activates a routing algorithm under a profile", "parameters": [ { - "name": "profile_id", + "name": "id", "in": "path", "description": "The unique identifier for the profile", "required": true, @@ -921,7 +982,7 @@ ] } }, - "/v2/profiles/{profile_id}/deactivate_routing_algorithm": { + "/v2/profiles/{id}/deactivate-routing-algorithm": { "patch": { "tags": [ "Profile" @@ -931,7 +992,7 @@ "operationId": " Deactivates a routing algorithm under a profile", "parameters": [ { - "name": "profile_id", + "name": "id", "in": "path", "description": "The unique identifier for the profile", "required": true, @@ -974,7 +1035,7 @@ ] } }, - "/v2/profiles/{profile_id}/fallback_routing": { + "/v2/profiles/{id}/fallback-routing": { "patch": { "tags": [ "Profile" @@ -984,7 +1045,7 @@ "operationId": "Update the default fallback routing algorithm for the profile", "parameters": [ { - "name": "profile_id", + "name": "id", "in": "path", "description": "The unique identifier for the profile", "required": true, @@ -1048,7 +1109,7 @@ "operationId": "Retrieve the default fallback routing algorithm for the profile", "parameters": [ { - "name": "profile_id", + "name": "id", "in": "path", "description": "The unique identifier for the profile", "required": true, @@ -1085,17 +1146,17 @@ ] } }, - "/v2/profiles/{profile_id}/routing_algorithm": { + "/v2/profiles/{id}/routing-algorithm": { "get": { "tags": [ "Profile" ], "summary": "Profile - Retrieve Active Routing Algorithm", - "description": "Retrieve active routing algorithm under the profile", + "description": "_\nRetrieve active routing algorithm under the profile", "operationId": "Retrieve the active routing algorithm under the profile", "parameters": [ { - "name": "profile_id", + "name": "id", "in": "path", "description": "The unique identifier for the profile", "required": true, @@ -1159,14 +1220,14 @@ ] } }, - "/v2/routing_algorithm": { + "/v2/routing-algorithm": { "post": { "tags": [ "Routing" ], "summary": "Routing - Create", "description": "Create a routing algorithm", - "operationId": "Create a routing algprithm", + "operationId": "Create a routing algorithm", "requestBody": { "content": { "application/json": { @@ -1214,7 +1275,7 @@ ] } }, - "/v2/routing_algorithm/{routing_algorithm_id}": { + "/v2/routing-algorithm/{id}": { "get": { "tags": [ "Routing" @@ -1224,7 +1285,7 @@ "operationId": "Retrieve a routing algorithm with its algorithm id", "parameters": [ { - "name": "routing_algorithm_id", + "name": "id", "in": "path", "description": "The unique identifier for a routing algorithm", "required": true, @@ -1264,7 +1325,7 @@ ] } }, - "/v2/api_keys": { + "/v2/api-keys": { "post": { "tags": [ "API Key" @@ -1304,7 +1365,7 @@ ] } }, - "/v2/api_keys/{key_id}": { + "/v2/api-keys/{id}": { "get": { "tags": [ "API Key" @@ -1314,7 +1375,7 @@ "operationId": "Retrieve an API Key", "parameters": [ { - "name": "key_id", + "name": "id", "in": "path", "description": "The unique identifier for the API Key", "required": true, @@ -1353,7 +1414,7 @@ "operationId": "Update an API Key", "parameters": [ { - "name": "key_id", + "name": "id", "in": "path", "description": "The unique identifier for the API Key", "required": true, @@ -1402,7 +1463,7 @@ "operationId": "Revoke an API Key", "parameters": [ { - "name": "key_id", + "name": "id", "in": "path", "description": "The unique identifier for the API Key", "required": true, @@ -1433,7 +1494,7 @@ ] } }, - "/v2/api_keys/list": { + "/v2/api-keys/list": { "get": { "tags": [ "API Key" @@ -1600,7 +1661,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CustomerRequest" + "$ref": "#/components/schemas/CustomerUpdateRequest" }, "examples": { "Update name and email of a customer": { @@ -1742,7 +1803,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PaymentsCreateIntentResponse" + "$ref": "#/components/schemas/PaymentsIntentResponse" } } } @@ -1757,1212 +1818,2283 @@ } ] } - } - }, - "components": { - "schemas": { - "AcceptanceType": { - "type": "string", - "description": "This is used to indicate if the mandate was accepted online or offline", - "enum": [ - "online", - "offline" - ] - }, - "AcceptedCountries": { - "oneOf": [ + }, + "/v2/payments/{id}/get-intent": { + "get": { + "tags": [ + "Payments" + ], + "summary": "Payments - Get Intent", + "description": "**Get a payment intent object when id is passed in path**\n\nYou will require the 'API - Key' from the Hyperswitch dashboard to make the call.", + "operationId": "Get the Payment Intent details", + "parameters": [ { - "type": "object", - "required": [ - "type", - "list" - ], - "properties": { - "type": { - "type": "string", - "enum": [ - "enable_only" - ] - }, - "list": { - "type": "array", - "items": { - "$ref": "#/components/schemas/CountryAlpha2" + "name": "id", + "in": "path", + "description": "The unique identifier for the Payment Intent", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Payment Intent", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentsIntentResponse" } } } }, + "404": { + "description": "Payment Intent not found" + } + }, + "security": [ { - "type": "object", - "required": [ - "type", - "list" - ], - "properties": { - "type": { - "type": "string", - "enum": [ - "disable_only" - ] - }, - "list": { - "type": "array", - "items": { - "$ref": "#/components/schemas/CountryAlpha2" - } - } + "api_key": [] + } + ] + } + }, + "/v2/payments/{id}/update-intent": { + "put": { + "tags": [ + "Payments" + ], + "summary": "Payments - Update Intent", + "description": "**Update a payment intent object**\n\nYou will require the 'API - Key' from the Hyperswitch dashboard to make the call.", + "operationId": "Update a Payment Intent", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The unique identifier for the Payment Intent", + "required": true, + "schema": { + "type": "string" } }, { - "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "type": "string", - "enum": [ - "all_accepted" - ] - } + "name": "X-Profile-Id", + "in": "header", + "description": "Profile ID associated to the payment intent", + "required": true, + "schema": { + "type": "string" + }, + "example": { + "X-Profile-Id": "pro_abcdefghijklmnop" } } ], - "description": "Object to filter the customer countries for which the payment method is displayed", - "discriminator": { - "propertyName": "type" - } - }, - "AcceptedCurrencies": { - "oneOf": [ - { - "type": "object", - "required": [ - "type", - "list" - ], - "properties": { - "type": { - "type": "string", - "enum": [ - "enable_only" - ] + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentsUpdateIntentRequest" }, - "list": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Currency" + "examples": { + "Update a payment intent with minimal fields": { + "value": { + "amount_details": { + "currency": "USD", + "order_amount": 6540 + } + } } } } }, - { - "type": "object", - "required": [ - "type", - "list" - ], - "properties": { - "type": { - "type": "string", - "enum": [ - "disable_only" - ] - }, - "list": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Currency" + "required": true + }, + "responses": { + "200": { + "description": "Payment Intent Updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentsIntentResponse" } } } }, + "404": { + "description": "Payment Intent Not Found" + } + }, + "security": [ { - "type": "object", - "required": [ - "type" - ], - "properties": { - "type": { - "type": "string", - "enum": [ - "all_accepted" - ] - } - } + "api_key": [] } + ] + } + }, + "/v2/payments/{id}/confirm-intent": { + "post": { + "tags": [ + "Payments" ], - "discriminator": { - "propertyName": "type" - } - }, - "AchBankDebitAdditionalData": { - "type": "object", - "required": [ - "account_number", - "routing_number" - ], - "properties": { - "account_number": { - "type": "string", - "description": "Partially masked account number for ach bank debit payment", - "example": "0001****3456" - }, - "routing_number": { - "type": "string", - "description": "Partially masked routing number for ach bank debit payment", - "example": "110***000" - }, - "card_holder_name": { - "type": "string", - "description": "Card holder's name", - "example": "John Doe", - "nullable": true + "summary": "Payments - Confirm Intent", + "description": "**Confirms a payment intent object with the payment method data**\n\n.", + "operationId": "Confirm Payment Intent", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The unique identifier for the Payment Intent", + "required": true, + "schema": { + "type": "string" + } }, - "bank_account_holder_name": { - "type": "string", - "description": "Bank account's owner name", - "example": "John Doe", - "nullable": true + { + "name": "X-Profile-Id", + "in": "header", + "description": "Profile ID associated to the payment intent", + "required": true, + "schema": { + "type": "string" + }, + "example": { + "X-Profile-Id": "pro_abcdefghijklmnop" + } }, - "bank_name": { - "allOf": [ - { - "$ref": "#/components/schemas/BankNames" + { + "name": "X-Client-Secret", + "in": "header", + "description": "Client Secret Associated with the payment intent", + "required": true, + "schema": { + "type": "string" + }, + "example": { + "X-Client-Secret": "12345_pay_0193e41106e07e518940f8b51b9c8121_secret_0193e41107027a928d61d292e6a5dba9" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentsConfirmIntentRequest" + }, + "examples": { + "Confirm the payment intent with card details": { + "value": { + "payment_method_data": { + "card": { + "card_cvc": "123", + "card_exp_month": "10", + "card_exp_year": "25", + "card_holder_name": "joseph Doe", + "card_number": "4242424242424242" + } + }, + "payment_method_type": "card" + } + } } - ], - "nullable": true + } }, - "bank_type": { - "allOf": [ - { - "$ref": "#/components/schemas/BankType" + "required": true + }, + "responses": { + "200": { + "description": "Payment created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentsConfirmIntentResponse" + } } - ], - "nullable": true + } }, - "bank_holder_type": { - "allOf": [ - { - "$ref": "#/components/schemas/BankHolderType" - } - ], - "nullable": true + "400": { + "description": "Missing Mandatory fields" } - } - }, - "AchBankTransfer": { - "type": "object", - "required": [ - "bank_account_number", - "bank_routing_number" + }, + "security": [ + { + "publishable_key": [] + } + ] + } + }, + "/v2/payments/{id}": { + "get": { + "tags": [ + "Payments" ], - "properties": { - "bank_name": { - "type": "string", - "description": "Bank name", - "example": "Deutsche Bank", - "nullable": true + "summary": "Payments - Get", + "description": "Retrieves a Payment. This API can also be used to get the status of a previously initiated payment or next action for an ongoing payment", + "operationId": "Retrieve a Payment", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The global payment id", + "required": true, + "schema": { + "type": "string" + } }, - "bank_country_code": { - "allOf": [ - { - "$ref": "#/components/schemas/CountryAlpha2" + { + "name": "force_sync", + "in": "query", + "description": "A boolean to indicate whether to force sync the payment status. Value can be true or false", + "required": true, + "schema": { + "$ref": "#/components/schemas/ForceSync" + } + } + ], + "responses": { + "200": { + "description": "Gets the payment with final status", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentsRetrieveResponse" + } } - ], - "nullable": true - }, - "bank_city": { - "type": "string", - "description": "Bank city", - "example": "California", - "nullable": true - }, - "bank_account_number": { - "type": "string", - "description": "Bank account number is an unique identifier assigned by a bank to a customer.", - "example": "000123456" + } }, - "bank_routing_number": { - "type": "string", - "description": "[9 digits] Routing number - used in USA for identifying a specific bank.", - "example": "110000000" + "404": { + "description": "No payment found with the given id" } - } - }, - "AchBankTransferAdditionalData": { - "type": "object", - "description": "Masked payout method details for ach bank transfer payout method", - "required": [ - "bank_account_number", - "bank_routing_number" + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/v2/payments/{payment_id}/create-external-sdk-tokens": { + "post": { + "tags": [ + "Payments" ], - "properties": { - "bank_account_number": { - "type": "string", - "description": "Partially masked account number for ach bank debit payment", - "example": "0001****3456" - }, - "bank_routing_number": { - "type": "string", - "description": "Partially masked routing number for ach bank debit payment", - "example": "110***000" - }, - "bank_name": { - "allOf": [ - { - "$ref": "#/components/schemas/BankNames" + "summary": "Payments - Session token", + "description": "Creates a session object or a session token for wallets like Apple Pay, Google Pay, etc. These tokens are used by Hyperswitch's SDK to initiate these wallets' SDK.", + "operationId": "Create Session tokens for 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/PaymentsSessionRequest" } - ], - "nullable": true + } }, - "bank_country_code": { - "allOf": [ - { - "$ref": "#/components/schemas/CountryAlpha2" + "required": true + }, + "responses": { + "200": { + "description": "Payment session object created or session token was retrieved from wallets", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentsSessionResponse" + } } - ], - "nullable": true + } }, - "bank_city": { - "type": "string", - "description": "Bank city", - "example": "California", - "nullable": true + "400": { + "description": "Missing mandatory fields" } - } - }, - "AchBillingDetails": { - "type": "object", - "properties": { - "email": { - "type": "string", - "description": "The Email ID for ACH billing", - "example": "example@me.com", - "nullable": true + }, + "security": [ + { + "publishable_key": [] } - } - }, - "AchTransfer": { - "type": "object", - "required": [ - "account_number", - "bank_name", - "routing_number", - "swift_code" + ] + } + }, + "/v2/payments/{id}/payment-methods": { + "get": { + "tags": [ + "Payments" ], - "properties": { - "account_number": { - "type": "string", - "example": "122385736258" - }, - "bank_name": { - "type": "string" + "summary": "Payments - Payment Methods List", + "description": "List the payment methods eligible for a payment. This endpoint also returns the saved payment methods for the customer when the customer_id is passed when creating the payment", + "operationId": "Retrieve Payment methods for a Payment", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The global payment id", + "required": true, + "schema": { + "type": "string" + } }, - "routing_number": { - "type": "string", - "example": "012" + { + "name": "X-Profile-Id", + "in": "header", + "description": "Profile ID associated to the payment intent", + "required": true, + "schema": { + "type": "string" + }, + "example": { + "X-Profile-Id": "pro_abcdefghijklmnop" + } }, - "swift_code": { - "type": "string", - "example": "234" - } - } - }, - "AdditionalMerchantData": { - "oneOf": [ { - "type": "object", - "required": [ - "open_banking_recipient_data" - ], - "properties": { - "open_banking_recipient_data": { - "$ref": "#/components/schemas/MerchantRecipientData" - } + "name": "X-Client-Secret", + "in": "header", + "description": "Client Secret Associated with the payment intent", + "required": true, + "schema": { + "type": "string" + }, + "example": { + "X-Client-Secret": "12345_pay_0193e41106e07e518940f8b51b9c8121_secret_0193e41107027a928d61d292e6a5dba9" } } - ] - }, - "AdditionalPayoutMethodData": { - "oneOf": [ - { - "type": "object", - "required": [ - "Card" - ], - "properties": { - "Card": { - "$ref": "#/components/schemas/CardAdditionalData" + ], + "responses": { + "200": { + "description": "Get the payment methods", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodListResponseForPayments" + } } } }, + "404": { + "description": "No payment found with the given id" + } + }, + "security": [ { - "type": "object", - "required": [ - "Bank" - ], - "properties": { - "Bank": { - "$ref": "#/components/schemas/BankAdditionalData" + "publishable_key": [] + } + ] + } + }, + "/v2/payments/{id}/saved-payment-methods": { + "get": { + "tags": [ + "Payment Methods" + ], + "summary": "List customer saved payment methods for a payment", + "description": "To filter and list the applicable payment methods for a particular Customer ID, is to be associated with a payment", + "operationId": "List all Payment Methods for a Customer", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodListRequest" } } }, - { - "type": "object", - "required": [ - "Wallet" - ], - "properties": { - "Wallet": { - "$ref": "#/components/schemas/WalletAdditionalData" + "required": true + }, + "responses": { + "200": { + "description": "Payment Methods retrieved for customer tied to its respective client-secret passed in the param", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerPaymentMethodsListResponse" + } } } + }, + "400": { + "description": "Invalid Data" + }, + "404": { + "description": "Payment Methods does not exist in records" + } + }, + "security": [ + { + "publishable_key": [] } + ] + } + }, + "/v2/customers/{id}/saved-payment-methods": { + "get": { + "tags": [ + "Payment Methods" ], - "description": "Masked payout method details for storing in db" - }, - "Address": { - "type": "object", - "properties": { - "address": { - "allOf": [ - { - "$ref": "#/components/schemas/AddressDetails" + "summary": "List saved payment methods for a Customer", + "description": "To filter and list the applicable payment methods for a particular Customer ID, to be used in a non-payments context", + "operationId": "List all Payment Methods for a Customer", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodListRequest" } - ], - "nullable": true + } }, - "phone": { - "allOf": [ - { - "$ref": "#/components/schemas/PhoneDetails" + "required": true + }, + "responses": { + "200": { + "description": "Payment Methods retrieved", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CustomerPaymentMethodsListResponse" + } } - ], - "nullable": true + } }, - "email": { - "type": "string", - "nullable": true + "400": { + "description": "Invalid Data" + }, + "404": { + "description": "Payment Methods does not exist in records" } }, - "additionalProperties": false - }, - "AddressDetails": { - "type": "object", - "description": "Address details", - "properties": { - "city": { - "type": "string", - "description": "The address city", - "example": "New York", - "nullable": true, - "maxLength": 50 - }, - "country": { - "allOf": [ - { - "$ref": "#/components/schemas/CountryAlpha2" + "security": [ + { + "api_key": [] + } + ] + } + }, + "/v2/payment-methods": { + "post": { + "tags": [ + "Payment Methods" + ], + "summary": "Payment Method - Create", + "description": "Creates and stores a payment method against a customer. In case of cards, this API should be used only by PCI compliant merchants.", + "operationId": "Create Payment Method", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodCreate" } - ], - "nullable": true - }, - "line1": { - "type": "string", - "description": "The first line of the address", - "example": "123, King Street", - "nullable": true, - "maxLength": 200 - }, - "line2": { - "type": "string", - "description": "The second line of the address", - "example": "Powelson Avenue", - "nullable": true, - "maxLength": 50 - }, - "line3": { - "type": "string", - "description": "The third line of the address", - "example": "Bridgewater", - "nullable": true, - "maxLength": 50 - }, - "zip": { - "type": "string", - "description": "The zip/postal code for the address", - "example": "08807", - "nullable": true, - "maxLength": 50 - }, - "state": { - "type": "string", - "description": "The address state", - "example": "New York", - "nullable": true + } }, - "first_name": { - "type": "string", - "description": "The first name for the address", - "example": "John", - "nullable": true, - "maxLength": 255 + "required": true + }, + "responses": { + "200": { + "description": "Payment Method Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodResponse" + } + } + } }, - "last_name": { - "type": "string", - "description": "The last name for the address", - "example": "Doe", - "nullable": true, - "maxLength": 255 + "400": { + "description": "Invalid Data" } }, - "additionalProperties": false - }, - "AirwallexData": { - "type": "object", - "properties": { - "payload": { - "type": "string", - "description": "payload required by airwallex", - "nullable": true + "security": [ + { + "api_key": [] } - } - }, - "AlfamartVoucherData": { - "type": "object", - "properties": { - "first_name": { - "type": "string", - "description": "The billing first name for Alfamart", - "example": "Jane", - "nullable": true + ] + } + }, + "/v2/payment-methods/create-intent": { + "post": { + "tags": [ + "Payment Methods" + ], + "summary": "Payment Method - Create Intent", + "description": "Creates a payment method for customer with billing information and other metadata.", + "operationId": "Create Payment Method Intent", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodIntentCreate" + } + } }, - "last_name": { - "type": "string", - "description": "The billing second name for Alfamart", - "example": "Doe", - "nullable": true + "required": true + }, + "responses": { + "200": { + "description": "Payment Method Intent Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodResponse" + } + } + } }, - "email": { - "type": "string", - "description": "The Email ID for Alfamart", - "example": "example@me.com", - "nullable": true + "400": { + "description": "Invalid Data" } - } - }, - "AliPayHkRedirection": { - "type": "object" - }, - "AliPayQr": { - "type": "object" - }, - "AliPayRedirection": { - "type": "object" - }, - "AmountDetails": { - "type": "object", - "required": [ - "currency" + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/v2/payment-methods/{id}/confirm-intent": { + "post": { + "tags": [ + "Payment Methods" ], - "properties": { - "order_amount": { - "type": "integer", - "format": "int64", - "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies)", - "example": 6540, - "minimum": 0 - }, - "currency": { - "$ref": "#/components/schemas/Currency" - }, - "shipping_cost": { - "allOf": [ - { - "$ref": "#/components/schemas/MinorUnit" + "summary": "Payment Method - Confirm Intent", + "description": "Update a payment method with customer's payment method related information.", + "operationId": "Confirm Payment Method Intent", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodIntentConfirm" } - ], - "nullable": true + } }, - "order_tax_amount": { - "allOf": [ - { - "$ref": "#/components/schemas/MinorUnit" + "required": true + }, + "responses": { + "200": { + "description": "Payment Method Intent Confirmed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodResponse" + } } - ], - "nullable": true - }, - "skip_external_tax_calculation": { - "$ref": "#/components/schemas/TaxCalculationOverride" - }, - "skip_surcharge_calculation": { - "$ref": "#/components/schemas/SurchargeCalculationOverride" + } }, - "surcharge_amount": { - "allOf": [ - { - "$ref": "#/components/schemas/MinorUnit" + "400": { + "description": "Invalid Data" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/v2/payment-methods/{id}/update-saved-payment-method": { + "patch": { + "tags": [ + "Payment Methods" + ], + "summary": "Payment Method - Update", + "description": "Update an existing payment method of a customer.", + "operationId": "Update Payment Method", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodUpdate" } - ], - "nullable": true + } }, - "tax_on_surcharge": { - "allOf": [ - { - "$ref": "#/components/schemas/MinorUnit" + "required": true + }, + "responses": { + "200": { + "description": "Payment Method Update", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodResponse" + } } - ], - "nullable": true - } - } - }, - "AmountFilter": { - "type": "object", - "properties": { - "start_amount": { - "type": "integer", - "format": "int64", - "description": "The start amount to filter list of transactions which are greater than or equal to the start amount", - "nullable": true + } }, - "end_amount": { - "type": "integer", - "format": "int64", - "description": "The end amount to filter list of transactions which are less than or equal to the end amount", - "nullable": true + "400": { + "description": "Invalid Data" } - } - }, - "AmountInfo": { - "type": "object", - "required": [ - "label", - "amount" + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/v2/payment-methods/{id}": { + "get": { + "tags": [ + "Payment Methods" ], - "properties": { - "label": { - "type": "string", - "description": "The label must be the name of the merchant." - }, - "type": { - "type": "string", - "description": "A value that indicates whether the line item(Ex: total, tax, discount, or grand total) is final or pending.", - "nullable": true + "summary": "Payment Method - Retrieve", + "description": "Retrieves a payment method of a customer.", + "operationId": "Retrieve Payment Method", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The unique identifier for the Payment Method", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Payment Method Retrieved", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodResponse" + } + } + } }, - "amount": { - "type": "string", - "description": "The total amount for the payment in majot unit string (Ex: 38.02)", - "example": "38.02" + "404": { + "description": "Payment Method Not Found" } - } + }, + "security": [ + { + "api_key": [] + } + ] }, - "ApiKeyExpiration": { - "oneOf": [ + "delete": { + "tags": [ + "Payment Methods" + ], + "summary": "Payment Method - Delete", + "description": "Deletes a payment method of a customer.", + "operationId": "Delete Payment Method", + "parameters": [ { - "type": "string", - "enum": [ - "never" - ] + "name": "id", + "in": "path", + "description": "The unique identifier for the Payment Method", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Payment Method Retrieved", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodDeleteResponse" + } + } + } }, + "404": { + "description": "Payment Method Not Found" + } + }, + "security": [ { - "type": "string", - "format": "date-time" + "api_key": [] } ] - }, - "ApplePayAddressParameters": { + } + }, + "/v2/refunds": { + "post": { + "tags": [ + "Refunds" + ], + "summary": "Refunds - Create", + "description": "Creates a refund against an already processed payment. In case of some processors, you can even opt to refund only a partial amount multiple times until the original charge amount has been refunded", + "operationId": "Create a Refund", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RefundsCreateRequest" + }, + "examples": { + "Create an instant refund to refund partial amount": { + "value": { + "amount": 654, + "merchant_reference_id": "ref_123", + "payment_id": "{{payment_id}}", + "refund_type": "instant" + } + }, + "Create an instant refund to refund the whole amount": { + "value": { + "merchant_reference_id": "ref_123", + "payment_id": "{{payment_id}}", + "refund_type": "instant" + } + }, + "Create an instant refund with reason": { + "value": { + "amount": 6540, + "merchant_reference_id": "ref_123", + "payment_id": "{{payment_id}}", + "reason": "Customer returned product", + "refund_type": "instant" + } + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Refund created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RefundResponse" + } + } + } + }, + "400": { + "description": "Missing Mandatory fields" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + } + }, + "components": { + "schemas": { + "AcceptanceType": { "type": "string", + "description": "This is used to indicate if the mandate was accepted online or offline", "enum": [ - "postalAddress", - "phone", - "email" + "online", + "offline" ] }, - "ApplePayBillingContactFields": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ApplePayAddressParameters" + "AcceptedCountries": { + "oneOf": [ + { + "type": "object", + "required": [ + "type", + "list" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "enable_only" + ] + }, + "list": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CountryAlpha2" + } + } + } + }, + { + "type": "object", + "required": [ + "type", + "list" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "disable_only" + ] + }, + "list": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CountryAlpha2" + } + } + } + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "all_accepted" + ] + } + } + } + ], + "description": "Object to filter the customer countries for which the payment method is displayed", + "discriminator": { + "propertyName": "type" } }, - "ApplePayPaymentRequest": { + "AcceptedCurrencies": { + "oneOf": [ + { + "type": "object", + "required": [ + "type", + "list" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "enable_only" + ] + }, + "list": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Currency" + } + } + } + }, + { + "type": "object", + "required": [ + "type", + "list" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "disable_only" + ] + }, + "list": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Currency" + } + } + } + }, + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "all_accepted" + ] + } + } + } + ], + "discriminator": { + "propertyName": "type" + } + }, + "AchBankDebitAdditionalData": { "type": "object", "required": [ - "country_code", - "currency_code", - "total" + "account_number", + "routing_number" ], "properties": { - "country_code": { - "$ref": "#/components/schemas/CountryAlpha2" - }, - "currency_code": { - "$ref": "#/components/schemas/Currency" + "account_number": { + "type": "string", + "description": "Partially masked account number for ach bank debit payment", + "example": "0001****3456" }, - "total": { - "$ref": "#/components/schemas/AmountInfo" + "routing_number": { + "type": "string", + "description": "Partially masked routing number for ach bank debit payment", + "example": "110***000" }, - "merchant_capabilities": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The list of merchant capabilities(ex: whether capable of 3ds or no-3ds)", + "card_holder_name": { + "type": "string", + "description": "Card holder's name", + "example": "John Doe", "nullable": true }, - "supported_networks": { - "type": "array", - "items": { - "type": "string" - }, - "description": "The list of supported networks", + "bank_account_holder_name": { + "type": "string", + "description": "Bank account's owner name", + "example": "John Doe", "nullable": true }, - "merchant_identifier": { - "type": "string", + "bank_name": { + "allOf": [ + { + "$ref": "#/components/schemas/BankNames" + } + ], "nullable": true }, - "required_billing_contact_fields": { + "bank_type": { "allOf": [ { - "$ref": "#/components/schemas/ApplePayBillingContactFields" + "$ref": "#/components/schemas/BankType" } ], "nullable": true }, - "required_shipping_contact_fields": { + "bank_holder_type": { "allOf": [ { - "$ref": "#/components/schemas/ApplePayShippingContactFields" + "$ref": "#/components/schemas/BankHolderType" } ], "nullable": true } } }, - "ApplePayRedirectData": { - "type": "object" - }, - "ApplePaySessionResponse": { - "oneOf": [ - { - "$ref": "#/components/schemas/ThirdPartySdkSessionResponse" - }, - { - "$ref": "#/components/schemas/NoThirdPartySdkSessionResponse" - }, - { - "type": "object", - "default": null, - "nullable": true - } - ] - }, - "ApplePayShippingContactFields": { - "type": "array", - "items": { - "$ref": "#/components/schemas/ApplePayAddressParameters" - } - }, - "ApplePayThirdPartySdkData": { - "type": "object" - }, - "ApplePayWalletData": { + "AchBankTransfer": { "type": "object", "required": [ - "payment_data", - "payment_method", - "transaction_identifier" + "bank_account_number", + "bank_routing_number" ], "properties": { - "payment_data": { + "bank_name": { "type": "string", - "description": "The payment data of Apple pay" - }, - "payment_method": { - "$ref": "#/components/schemas/ApplepayPaymentMethod" + "description": "Bank name", + "example": "Deutsche Bank", + "nullable": true }, - "transaction_identifier": { - "type": "string", - "description": "The unique identifier for the transaction" - } - } - }, - "ApplepayConnectorMetadataRequest": { - "type": "object", - "properties": { - "session_token_data": { + "bank_country_code": { "allOf": [ { - "$ref": "#/components/schemas/SessionTokenInfo" + "$ref": "#/components/schemas/CountryAlpha2" } ], "nullable": true - } - } - }, - "ApplepayInitiative": { - "type": "string", - "enum": [ - "web", - "ios" - ] - }, - "ApplepayPaymentMethod": { - "type": "object", - "required": [ - "display_name", - "network", - "type" - ], - "properties": { - "display_name": { + }, + "bank_city": { "type": "string", - "description": "The name to be displayed on Apple Pay button" + "description": "Bank city", + "example": "California", + "nullable": true }, - "network": { + "bank_account_number": { "type": "string", - "description": "The network of the Apple pay payment method" + "description": "Bank account number is an unique identifier assigned by a bank to a customer.", + "example": "000123456" }, - "type": { + "bank_routing_number": { "type": "string", - "description": "The type of the payment method" + "description": "[9 digits] Routing number - used in USA for identifying a specific bank.", + "example": "110000000" } } }, - "ApplepaySessionTokenResponse": { + "AchBankTransferAdditionalData": { "type": "object", + "description": "Masked payout method details for ach bank transfer payout method", "required": [ - "connector", - "delayed_session_token", - "sdk_next_action" + "bank_account_number", + "bank_routing_number" ], "properties": { - "session_token_data": { + "bank_account_number": { + "type": "string", + "description": "Partially masked account number for ach bank debit payment", + "example": "0001****3456" + }, + "bank_routing_number": { + "type": "string", + "description": "Partially masked routing number for ach bank debit payment", + "example": "110***000" + }, + "bank_name": { "allOf": [ { - "$ref": "#/components/schemas/ApplePaySessionResponse" + "$ref": "#/components/schemas/BankNames" } ], "nullable": true }, - "payment_request_data": { + "bank_country_code": { "allOf": [ { - "$ref": "#/components/schemas/ApplePayPaymentRequest" + "$ref": "#/components/schemas/CountryAlpha2" } ], "nullable": true }, - "connector": { - "type": "string", - "description": "The session token is w.r.t this connector" - }, - "delayed_session_token": { - "type": "boolean", - "description": "Identifier for the delayed session response" - }, - "sdk_next_action": { - "$ref": "#/components/schemas/SdkNextAction" - }, - "connector_reference_id": { - "type": "string", - "description": "The connector transaction id", - "nullable": true - }, - "connector_sdk_public_key": { - "type": "string", - "description": "The public key id is to invoke third party sdk", - "nullable": true - }, - "connector_merchant_id": { + "bank_city": { "type": "string", - "description": "The connector merchant id", + "description": "Bank city", + "example": "California", "nullable": true } } }, - "AttemptStatus": { - "type": "string", - "description": "The status of the attempt", - "enum": [ - "started", - "authentication_failed", - "router_declined", - "authentication_pending", - "authentication_successful", - "authorized", - "authorization_failed", - "charged", - "authorizing", - "cod_initiated", - "voided", - "void_initiated", - "capture_initiated", - "capture_failed", - "void_failed", - "auto_refunded", - "partial_charged", - "partial_charged_and_chargeable", - "unresolved", - "pending", - "failure", - "payment_method_awaited", - "confirmation_awaited", - "device_data_collection_pending" - ] - }, - "AuthenticationConnectorDetails": { + "AchBillingDetails": { "type": "object", - "required": [ - "authentication_connectors", - "three_ds_requestor_url" - ], "properties": { - "authentication_connectors": { - "type": "array", - "items": { - "$ref": "#/components/schemas/AuthenticationConnectors" - }, - "description": "List of authentication connectors" - }, - "three_ds_requestor_url": { + "email": { "type": "string", - "description": "URL of the (customer service) website that will be shown to the shopper in case of technical errors during the 3D Secure 2 process." + "description": "The Email ID for ACH billing", + "example": "example@me.com", + "nullable": true } } }, - "AuthenticationConnectors": { - "type": "string", - "enum": [ - "threedsecureio", - "netcetera", - "gpayments" - ] - }, - "AuthenticationStatus": { - "type": "string", - "enum": [ - "started", - "pending", - "success", - "failed" - ] - }, - "AuthenticationType": { - "type": "string", - "description": "Pass this parameter to force 3DS or non 3DS auth for this payment. Some connectors will still force 3DS auth even in case of passing 'no_three_ds' here and vice versa. Default value is 'no_three_ds' if not set", - "enum": [ - "three_ds", - "no_three_ds" - ] - }, - "AuthorizationStatus": { - "type": "string", - "enum": [ - "success", - "failure", - "processing", - "unresolved" - ] - }, - "BacsBankDebitAdditionalData": { + "AchTransfer": { "type": "object", "required": [ "account_number", - "sort_code" + "bank_name", + "routing_number", + "swift_code" ], "properties": { "account_number": { "type": "string", - "description": "Partially masked account number for Bacs payment method", - "example": "0001****3456" + "example": "122385736258" }, - "sort_code": { + "bank_name": { + "type": "string" + }, + "routing_number": { "type": "string", - "description": "Partially masked sort code for Bacs payment method", - "example": "108800" + "example": "012" }, - "bank_account_holder_name": { + "swift_code": { "type": "string", - "description": "Bank account's owner name", - "example": "John Doe", - "nullable": true + "example": "234" } } }, - "BacsBankTransfer": { - "type": "object", - "required": [ - "bank_account_number", - "bank_sort_code" - ], - "properties": { - "bank_name": { - "type": "string", - "description": "Bank name", - "example": "Deutsche Bank", - "nullable": true - }, - "bank_country_code": { - "allOf": [ - { - "$ref": "#/components/schemas/CountryAlpha2" - } - ], - "nullable": true - }, - "bank_city": { - "type": "string", - "description": "Bank city", - "example": "California", - "nullable": true - }, - "bank_account_number": { - "type": "string", - "description": "Bank account number is an unique identifier assigned by a bank to a customer.", - "example": "000123456" - }, - "bank_sort_code": { - "type": "string", - "description": "[6 digits] Sort Code - used in UK and Ireland for identifying a bank and it's branches.", - "example": "98-76-54" - } - } - }, - "BacsBankTransferAdditionalData": { - "type": "object", - "description": "Masked payout method details for bacs bank transfer payout method", - "required": [ - "bank_sort_code", - "bank_account_number" - ], - "properties": { - "bank_sort_code": { - "type": "string", - "description": "Partially masked sort code for Bacs payment method", - "example": "108800" - }, - "bank_account_number": { - "type": "string", - "description": "Bank account's owner name", - "example": "0001****3456" - }, - "bank_name": { - "type": "string", - "description": "Bank name", - "example": "Deutsche Bank", - "nullable": true - }, - "bank_country_code": { - "allOf": [ - { - "$ref": "#/components/schemas/CountryAlpha2" - } - ], - "nullable": true - }, - "bank_city": { - "type": "string", - "description": "Bank city", - "example": "California", - "nullable": true - } - } - }, - "BacsBankTransferInstructions": { - "type": "object", - "required": [ - "account_holder_name", - "account_number", - "sort_code" - ], - "properties": { - "account_holder_name": { - "type": "string", - "example": "Jane Doe" - }, - "account_number": { - "type": "string", - "example": "10244123908" - }, - "sort_code": { - "type": "string", - "example": "012" - } - } - }, - "BancontactBankRedirectAdditionalData": { - "type": "object", - "properties": { - "last4": { - "type": "string", - "description": "Last 4 digits of the card number", - "example": "4242", - "nullable": true - }, - "card_exp_month": { - "type": "string", - "description": "The card's expiry month", - "example": "12", - "nullable": true - }, - "card_exp_year": { - "type": "string", - "description": "The card's expiry year", - "example": "24", - "nullable": true - }, - "card_holder_name": { - "type": "string", - "description": "The card holder's name", - "example": "John Test", - "nullable": true - } - } - }, - "Bank": { - "oneOf": [ - { - "$ref": "#/components/schemas/AchBankTransfer" - }, - { - "$ref": "#/components/schemas/BacsBankTransfer" - }, - { - "$ref": "#/components/schemas/SepaBankTransfer" - }, - { - "$ref": "#/components/schemas/PixBankTransfer" - } - ] - }, - "BankAdditionalData": { - "oneOf": [ - { - "$ref": "#/components/schemas/AchBankTransferAdditionalData" - }, - { - "$ref": "#/components/schemas/BacsBankTransferAdditionalData" - }, - { - "$ref": "#/components/schemas/SepaBankTransferAdditionalData" - }, - { - "$ref": "#/components/schemas/PixBankTransferAdditionalData" - } - ], - "description": "Masked payout method details for bank payout method" - }, - "BankDebitAdditionalData": { + "AdditionalMerchantData": { "oneOf": [ { "type": "object", "required": [ - "ach" + "open_banking_recipient_data" ], "properties": { - "ach": { - "$ref": "#/components/schemas/AchBankDebitAdditionalData" + "open_banking_recipient_data": { + "$ref": "#/components/schemas/MerchantRecipientData" } } - }, + } + ] + }, + "AdditionalPayoutMethodData": { + "oneOf": [ { "type": "object", "required": [ - "bacs" + "Card" ], "properties": { - "bacs": { - "$ref": "#/components/schemas/BacsBankDebitAdditionalData" + "Card": { + "$ref": "#/components/schemas/CardAdditionalData" } } }, { "type": "object", "required": [ - "becs" + "Bank" ], "properties": { - "becs": { - "$ref": "#/components/schemas/BecsBankDebitAdditionalData" + "Bank": { + "$ref": "#/components/schemas/BankAdditionalData" } } }, { "type": "object", "required": [ - "sepa" + "Wallet" ], "properties": { - "sepa": { - "$ref": "#/components/schemas/SepaBankDebitAdditionalData" + "Wallet": { + "$ref": "#/components/schemas/WalletAdditionalData" } } } - ] + ], + "description": "Masked payout method details for storing in db" }, - "BankDebitBilling": { + "Address": { "type": "object", "properties": { - "name": { - "type": "string", - "description": "The billing name for bank debits", - "example": "John Doe", + "address": { + "allOf": [ + { + "$ref": "#/components/schemas/AddressDetails" + } + ], + "nullable": true + }, + "phone": { + "allOf": [ + { + "$ref": "#/components/schemas/PhoneDetails" + } + ], "nullable": true }, "email": { "type": "string", - "description": "The billing email for bank debits", - "example": "example@example.com", "nullable": true + } + }, + "additionalProperties": false + }, + "AddressDetails": { + "type": "object", + "description": "Address details", + "properties": { + "city": { + "type": "string", + "description": "The address city", + "example": "New York", + "nullable": true, + "maxLength": 50 }, - "address": { + "country": { "allOf": [ { - "$ref": "#/components/schemas/AddressDetails" + "$ref": "#/components/schemas/CountryAlpha2" } ], "nullable": true - } - } - }, - "BankDebitData": { - "oneOf": [ - { - "type": "object", - "required": [ - "ach_bank_debit" - ], - "properties": { - "ach_bank_debit": { - "type": "object", - "description": "Payment Method data for Ach bank debit", - "required": [ - "account_number", - "routing_number", - "card_holder_name", - "bank_account_holder_name", - "bank_name", - "bank_type", - "bank_holder_type" - ], - "properties": { - "billing_details": { - "allOf": [ - { - "$ref": "#/components/schemas/BankDebitBilling" - } - ], - "nullable": true - }, - "account_number": { - "type": "string", - "description": "Account number for ach bank debit payment", - "example": "000123456789" - }, - "routing_number": { - "type": "string", - "description": "Routing number for ach bank debit payment", + }, + "line1": { + "type": "string", + "description": "The first line of the address", + "example": "123, King Street", + "nullable": true, + "maxLength": 200 + }, + "line2": { + "type": "string", + "description": "The second line of the address", + "example": "Powelson Avenue", + "nullable": true, + "maxLength": 50 + }, + "line3": { + "type": "string", + "description": "The third line of the address", + "example": "Bridgewater", + "nullable": true, + "maxLength": 50 + }, + "zip": { + "type": "string", + "description": "The zip/postal code for the address", + "example": "08807", + "nullable": true, + "maxLength": 50 + }, + "state": { + "type": "string", + "description": "The address state", + "example": "New York", + "nullable": true + }, + "first_name": { + "type": "string", + "description": "The first name for the address", + "example": "John", + "nullable": true, + "maxLength": 255 + }, + "last_name": { + "type": "string", + "description": "The last name for the address", + "example": "Doe", + "nullable": true, + "maxLength": 255 + } + }, + "additionalProperties": false + }, + "AirwallexData": { + "type": "object", + "properties": { + "payload": { + "type": "string", + "description": "payload required by airwallex", + "nullable": true + } + } + }, + "AlfamartVoucherData": { + "type": "object", + "properties": { + "first_name": { + "type": "string", + "description": "The billing first name for Alfamart", + "example": "Jane", + "nullable": true + }, + "last_name": { + "type": "string", + "description": "The billing second name for Alfamart", + "example": "Doe", + "nullable": true + }, + "email": { + "type": "string", + "description": "The Email ID for Alfamart", + "example": "example@me.com", + "nullable": true + } + } + }, + "AliPayHkRedirection": { + "type": "object" + }, + "AliPayQr": { + "type": "object" + }, + "AliPayRedirection": { + "type": "object" + }, + "AmountDetails": { + "type": "object", + "required": [ + "currency" + ], + "properties": { + "order_amount": { + "type": "integer", + "format": "int64", + "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies)", + "example": 6540, + "minimum": 0 + }, + "currency": { + "$ref": "#/components/schemas/Currency" + }, + "shipping_cost": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "order_tax_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "skip_external_tax_calculation": { + "$ref": "#/components/schemas/TaxCalculationOverride" + }, + "skip_surcharge_calculation": { + "$ref": "#/components/schemas/SurchargeCalculationOverride" + }, + "surcharge_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "tax_on_surcharge": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + } + } + }, + "AmountDetailsResponse": { + "type": "object", + "required": [ + "order_amount", + "currency", + "external_tax_calculation", + "surcharge_calculation" + ], + "properties": { + "order_amount": { + "type": "integer", + "format": "int64", + "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies)", + "example": 6540, + "minimum": 0 + }, + "currency": { + "$ref": "#/components/schemas/Currency" + }, + "shipping_cost": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "order_tax_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "external_tax_calculation": { + "$ref": "#/components/schemas/TaxCalculationOverride" + }, + "surcharge_calculation": { + "$ref": "#/components/schemas/SurchargeCalculationOverride" + }, + "surcharge_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "tax_on_surcharge": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + } + } + }, + "AmountDetailsUpdate": { + "type": "object", + "properties": { + "order_amount": { + "type": "integer", + "format": "int64", + "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies)", + "example": 6540, + "nullable": true, + "minimum": 0 + }, + "currency": { + "allOf": [ + { + "$ref": "#/components/schemas/Currency" + } + ], + "nullable": true + }, + "shipping_cost": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "order_tax_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "skip_external_tax_calculation": { + "allOf": [ + { + "$ref": "#/components/schemas/TaxCalculationOverride" + } + ], + "nullable": true + }, + "skip_surcharge_calculation": { + "allOf": [ + { + "$ref": "#/components/schemas/SurchargeCalculationOverride" + } + ], + "nullable": true + }, + "surcharge_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "tax_on_surcharge": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + } + } + }, + "AmountFilter": { + "type": "object", + "properties": { + "start_amount": { + "type": "integer", + "format": "int64", + "description": "The start amount to filter list of transactions which are greater than or equal to the start amount", + "nullable": true + }, + "end_amount": { + "type": "integer", + "format": "int64", + "description": "The end amount to filter list of transactions which are less than or equal to the end amount", + "nullable": true + } + } + }, + "AmountInfo": { + "type": "object", + "required": [ + "label", + "amount" + ], + "properties": { + "label": { + "type": "string", + "description": "The label must be the name of the merchant." + }, + "type": { + "type": "string", + "description": "A value that indicates whether the line item(Ex: total, tax, discount, or grand total) is final or pending.", + "nullable": true + }, + "amount": { + "type": "string", + "description": "The total amount for the payment in majot unit string (Ex: 38.02)", + "example": "38.02" + } + } + }, + "ApiKeyExpiration": { + "oneOf": [ + { + "type": "string", + "enum": [ + "never" + ] + }, + { + "type": "string", + "format": "date-time" + } + ] + }, + "ApplePayAddressParameters": { + "type": "string", + "enum": [ + "postalAddress", + "phone", + "email" + ] + }, + "ApplePayBillingContactFields": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplePayAddressParameters" + } + }, + "ApplePayPaymentRequest": { + "type": "object", + "required": [ + "country_code", + "currency_code", + "total" + ], + "properties": { + "country_code": { + "$ref": "#/components/schemas/CountryAlpha2" + }, + "currency_code": { + "$ref": "#/components/schemas/Currency" + }, + "total": { + "$ref": "#/components/schemas/AmountInfo" + }, + "merchant_capabilities": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The list of merchant capabilities(ex: whether capable of 3ds or no-3ds)", + "nullable": true + }, + "supported_networks": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The list of supported networks", + "nullable": true + }, + "merchant_identifier": { + "type": "string", + "nullable": true + }, + "required_billing_contact_fields": { + "allOf": [ + { + "$ref": "#/components/schemas/ApplePayBillingContactFields" + } + ], + "nullable": true + }, + "required_shipping_contact_fields": { + "allOf": [ + { + "$ref": "#/components/schemas/ApplePayShippingContactFields" + } + ], + "nullable": true + }, + "recurring_payment_request": { + "allOf": [ + { + "$ref": "#/components/schemas/ApplePayRecurringPaymentRequest" + } + ], + "nullable": true + } + } + }, + "ApplePayPaymentTiming": { + "type": "string", + "enum": [ + "immediate", + "recurring" + ] + }, + "ApplePayRecurringDetails": { + "type": "object", + "required": [ + "payment_description", + "regular_billing", + "management_url" + ], + "properties": { + "payment_description": { + "type": "string", + "description": "A description of the recurring payment that Apple Pay displays to the user in the payment sheet" + }, + "regular_billing": { + "$ref": "#/components/schemas/ApplePayRegularBillingDetails" + }, + "billing_agreement": { + "type": "string", + "description": "A localized billing agreement that the payment sheet displays to the user before the user authorizes the payment", + "nullable": true + }, + "management_url": { + "type": "string", + "description": "A URL to a web page where the user can update or delete the payment method for the recurring payment", + "example": "https://hyperswitch.io" + } + } + }, + "ApplePayRecurringPaymentRequest": { + "type": "object", + "required": [ + "payment_description", + "regular_billing", + "management_url" + ], + "properties": { + "payment_description": { + "type": "string", + "description": "A description of the recurring payment that Apple Pay displays to the user in the payment sheet" + }, + "regular_billing": { + "$ref": "#/components/schemas/ApplePayRegularBillingRequest" + }, + "billing_agreement": { + "type": "string", + "description": "A localized billing agreement that the payment sheet displays to the user before the user authorizes the payment", + "nullable": true + }, + "management_url": { + "type": "string", + "description": "A URL to a web page where the user can update or delete the payment method for the recurring payment", + "example": "https://hyperswitch.io" + } + } + }, + "ApplePayRedirectData": { + "type": "object" + }, + "ApplePayRegularBillingDetails": { + "type": "object", + "required": [ + "label" + ], + "properties": { + "label": { + "type": "string", + "description": "The label that Apple Pay displays to the user in the payment sheet with the recurring details" + }, + "recurring_payment_start_date": { + "type": "string", + "format": "date-time", + "description": "The date of the first payment", + "example": "2023-09-10T23:59:59Z", + "nullable": true + }, + "recurring_payment_end_date": { + "type": "string", + "format": "date-time", + "description": "The date of the final payment", + "example": "2023-09-10T23:59:59Z", + "nullable": true + }, + "recurring_payment_interval_unit": { + "allOf": [ + { + "$ref": "#/components/schemas/RecurringPaymentIntervalUnit" + } + ], + "nullable": true + }, + "recurring_payment_interval_count": { + "type": "integer", + "format": "int32", + "description": "The number of interval units that make up the total payment interval", + "nullable": true + } + } + }, + "ApplePayRegularBillingRequest": { + "type": "object", + "required": [ + "amount", + "label", + "payment_timing" + ], + "properties": { + "amount": { + "type": "string", + "description": "The amount of the recurring payment", + "example": "38.02" + }, + "label": { + "type": "string", + "description": "The label that Apple Pay displays to the user in the payment sheet with the recurring details" + }, + "payment_timing": { + "$ref": "#/components/schemas/ApplePayPaymentTiming" + }, + "recurring_payment_start_date": { + "type": "string", + "format": "date-time", + "description": "The date of the first payment", + "nullable": true + }, + "recurring_payment_end_date": { + "type": "string", + "format": "date-time", + "description": "The date of the final payment", + "nullable": true + }, + "recurring_payment_interval_unit": { + "allOf": [ + { + "$ref": "#/components/schemas/RecurringPaymentIntervalUnit" + } + ], + "nullable": true + }, + "recurring_payment_interval_count": { + "type": "integer", + "format": "int32", + "description": "The number of interval units that make up the total payment interval", + "nullable": true + } + } + }, + "ApplePaySessionResponse": { + "oneOf": [ + { + "$ref": "#/components/schemas/ThirdPartySdkSessionResponse" + }, + { + "$ref": "#/components/schemas/NoThirdPartySdkSessionResponse" + }, + { + "type": "object", + "default": null, + "nullable": true + } + ] + }, + "ApplePayShippingContactFields": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ApplePayAddressParameters" + } + }, + "ApplePayThirdPartySdkData": { + "type": "object" + }, + "ApplePayWalletData": { + "type": "object", + "required": [ + "payment_data", + "payment_method", + "transaction_identifier" + ], + "properties": { + "payment_data": { + "type": "string", + "description": "The payment data of Apple pay" + }, + "payment_method": { + "$ref": "#/components/schemas/ApplepayPaymentMethod" + }, + "transaction_identifier": { + "type": "string", + "description": "The unique identifier for the transaction" + } + } + }, + "ApplepayConnectorMetadataRequest": { + "type": "object", + "properties": { + "session_token_data": { + "allOf": [ + { + "$ref": "#/components/schemas/SessionTokenInfo" + } + ], + "nullable": true + } + } + }, + "ApplepayInitiative": { + "type": "string", + "enum": [ + "web", + "ios" + ] + }, + "ApplepayPaymentMethod": { + "type": "object", + "required": [ + "display_name", + "network", + "type" + ], + "properties": { + "display_name": { + "type": "string", + "description": "The name to be displayed on Apple Pay button" + }, + "network": { + "type": "string", + "description": "The network of the Apple pay payment method" + }, + "type": { + "type": "string", + "description": "The type of the payment method" + } + } + }, + "ApplepaySessionTokenResponse": { + "type": "object", + "required": [ + "connector", + "delayed_session_token", + "sdk_next_action" + ], + "properties": { + "session_token_data": { + "allOf": [ + { + "$ref": "#/components/schemas/ApplePaySessionResponse" + } + ], + "nullable": true + }, + "payment_request_data": { + "allOf": [ + { + "$ref": "#/components/schemas/ApplePayPaymentRequest" + } + ], + "nullable": true + }, + "connector": { + "type": "string", + "description": "The session token is w.r.t this connector" + }, + "delayed_session_token": { + "type": "boolean", + "description": "Identifier for the delayed session response" + }, + "sdk_next_action": { + "$ref": "#/components/schemas/SdkNextAction" + }, + "connector_reference_id": { + "type": "string", + "description": "The connector transaction id", + "nullable": true + }, + "connector_sdk_public_key": { + "type": "string", + "description": "The public key id is to invoke third party sdk", + "nullable": true + }, + "connector_merchant_id": { + "type": "string", + "description": "The connector merchant id", + "nullable": true + } + } + }, + "AttemptStatus": { + "type": "string", + "description": "The status of the attempt", + "enum": [ + "started", + "authentication_failed", + "router_declined", + "authentication_pending", + "authentication_successful", + "authorized", + "authorization_failed", + "charged", + "authorizing", + "cod_initiated", + "voided", + "void_initiated", + "capture_initiated", + "capture_failed", + "void_failed", + "auto_refunded", + "partial_charged", + "partial_charged_and_chargeable", + "unresolved", + "pending", + "failure", + "payment_method_awaited", + "confirmation_awaited", + "device_data_collection_pending" + ] + }, + "AuthenticationConnectorDetails": { + "type": "object", + "required": [ + "authentication_connectors", + "three_ds_requestor_url" + ], + "properties": { + "authentication_connectors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AuthenticationConnectors" + }, + "description": "List of authentication connectors" + }, + "three_ds_requestor_url": { + "type": "string", + "description": "URL of the (customer service) website that will be shown to the shopper in case of technical errors during the 3D Secure 2 process." + } + } + }, + "AuthenticationConnectors": { + "type": "string", + "enum": [ + "threedsecureio", + "netcetera", + "gpayments", + "ctp_mastercard", + "unified_authentication_service" + ] + }, + "AuthenticationStatus": { + "type": "string", + "enum": [ + "started", + "pending", + "success", + "failed" + ] + }, + "AuthenticationType": { + "type": "string", + "description": "Pass this parameter to force 3DS or non 3DS auth for this payment. Some connectors will still force 3DS auth even in case of passing 'no_three_ds' here and vice versa. Default value is 'no_three_ds' if not set", + "enum": [ + "three_ds", + "no_three_ds" + ] + }, + "AuthorizationStatus": { + "type": "string", + "enum": [ + "success", + "failure", + "processing", + "unresolved" + ] + }, + "BacsBankDebitAdditionalData": { + "type": "object", + "required": [ + "account_number", + "sort_code" + ], + "properties": { + "account_number": { + "type": "string", + "description": "Partially masked account number for Bacs payment method", + "example": "0001****3456" + }, + "sort_code": { + "type": "string", + "description": "Partially masked sort code for Bacs payment method", + "example": "108800" + }, + "bank_account_holder_name": { + "type": "string", + "description": "Bank account's owner name", + "example": "John Doe", + "nullable": true + } + } + }, + "BacsBankTransfer": { + "type": "object", + "required": [ + "bank_account_number", + "bank_sort_code" + ], + "properties": { + "bank_name": { + "type": "string", + "description": "Bank name", + "example": "Deutsche Bank", + "nullable": true + }, + "bank_country_code": { + "allOf": [ + { + "$ref": "#/components/schemas/CountryAlpha2" + } + ], + "nullable": true + }, + "bank_city": { + "type": "string", + "description": "Bank city", + "example": "California", + "nullable": true + }, + "bank_account_number": { + "type": "string", + "description": "Bank account number is an unique identifier assigned by a bank to a customer.", + "example": "000123456" + }, + "bank_sort_code": { + "type": "string", + "description": "[6 digits] Sort Code - used in UK and Ireland for identifying a bank and it's branches.", + "example": "98-76-54" + } + } + }, + "BacsBankTransferAdditionalData": { + "type": "object", + "description": "Masked payout method details for bacs bank transfer payout method", + "required": [ + "bank_sort_code", + "bank_account_number" + ], + "properties": { + "bank_sort_code": { + "type": "string", + "description": "Partially masked sort code for Bacs payment method", + "example": "108800" + }, + "bank_account_number": { + "type": "string", + "description": "Bank account's owner name", + "example": "0001****3456" + }, + "bank_name": { + "type": "string", + "description": "Bank name", + "example": "Deutsche Bank", + "nullable": true + }, + "bank_country_code": { + "allOf": [ + { + "$ref": "#/components/schemas/CountryAlpha2" + } + ], + "nullable": true + }, + "bank_city": { + "type": "string", + "description": "Bank city", + "example": "California", + "nullable": true + } + } + }, + "BacsBankTransferInstructions": { + "type": "object", + "required": [ + "account_holder_name", + "account_number", + "sort_code" + ], + "properties": { + "account_holder_name": { + "type": "string", + "example": "Jane Doe" + }, + "account_number": { + "type": "string", + "example": "10244123908" + }, + "sort_code": { + "type": "string", + "example": "012" + } + } + }, + "BancontactBankRedirectAdditionalData": { + "type": "object", + "properties": { + "last4": { + "type": "string", + "description": "Last 4 digits of the card number", + "example": "4242", + "nullable": true + }, + "card_exp_month": { + "type": "string", + "description": "The card's expiry month", + "example": "12", + "nullable": true + }, + "card_exp_year": { + "type": "string", + "description": "The card's expiry year", + "example": "24", + "nullable": true + }, + "card_holder_name": { + "type": "string", + "description": "The card holder's name", + "example": "John Test", + "nullable": true + } + } + }, + "Bank": { + "oneOf": [ + { + "$ref": "#/components/schemas/AchBankTransfer" + }, + { + "$ref": "#/components/schemas/BacsBankTransfer" + }, + { + "$ref": "#/components/schemas/SepaBankTransfer" + }, + { + "$ref": "#/components/schemas/PixBankTransfer" + } + ] + }, + "BankAdditionalData": { + "oneOf": [ + { + "$ref": "#/components/schemas/AchBankTransferAdditionalData" + }, + { + "$ref": "#/components/schemas/BacsBankTransferAdditionalData" + }, + { + "$ref": "#/components/schemas/SepaBankTransferAdditionalData" + }, + { + "$ref": "#/components/schemas/PixBankTransferAdditionalData" + } + ], + "description": "Masked payout method details for bank payout method" + }, + "BankCodeResponse": { + "type": "object", + "required": [ + "bank_name", + "eligible_connectors" + ], + "properties": { + "bank_name": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BankNames" + } + }, + "eligible_connectors": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "BankDebitAdditionalData": { + "oneOf": [ + { + "type": "object", + "required": [ + "ach" + ], + "properties": { + "ach": { + "$ref": "#/components/schemas/AchBankDebitAdditionalData" + } + } + }, + { + "type": "object", + "required": [ + "bacs" + ], + "properties": { + "bacs": { + "$ref": "#/components/schemas/BacsBankDebitAdditionalData" + } + } + }, + { + "type": "object", + "required": [ + "becs" + ], + "properties": { + "becs": { + "$ref": "#/components/schemas/BecsBankDebitAdditionalData" + } + } + }, + { + "type": "object", + "required": [ + "sepa" + ], + "properties": { + "sepa": { + "$ref": "#/components/schemas/SepaBankDebitAdditionalData" + } + } + } + ] + }, + "BankDebitBilling": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The billing name for bank debits", + "example": "John Doe", + "nullable": true + }, + "email": { + "type": "string", + "description": "The billing email for bank debits", + "example": "example@example.com", + "nullable": true + }, + "address": { + "allOf": [ + { + "$ref": "#/components/schemas/AddressDetails" + } + ], + "nullable": true + } + } + }, + "BankDebitData": { + "oneOf": [ + { + "type": "object", + "required": [ + "ach_bank_debit" + ], + "properties": { + "ach_bank_debit": { + "type": "object", + "description": "Payment Method data for Ach bank debit", + "required": [ + "account_number", + "routing_number", + "card_holder_name", + "bank_account_holder_name", + "bank_name", + "bank_type", + "bank_holder_type" + ], + "properties": { + "billing_details": { + "allOf": [ + { + "$ref": "#/components/schemas/BankDebitBilling" + } + ], + "nullable": true + }, + "account_number": { + "type": "string", + "description": "Account number for ach bank debit payment", + "example": "000123456789" + }, + "routing_number": { + "type": "string", + "description": "Routing number for ach bank debit payment", "example": "110000000" }, "card_holder_name": { @@ -3123,6 +4255,20 @@ } ] }, + "BankDebitTypes": { + "type": "object", + "required": [ + "eligible_connectors" + ], + "properties": { + "eligible_connectors": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "BankHolderType": { "type": "string", "enum": [ @@ -3431,7 +4577,7 @@ }, "bank_account_bic": { "type": "string", - "description": "Bank account details for Giropay\nBank account bic code", + "description": "Bank account bic code", "nullable": true }, "bank_account_iban": { @@ -4346,6 +5492,25 @@ } ] }, + "BankTransferTypes": { + "type": "object", + "required": [ + "eligible_connectors" + ], + "properties": { + "eligible_connectors": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The list of eligible connectors for a given payment experience", + "example": [ + "stripe", + "adyen" + ] + } + } + }, "BankType": { "type": "string", "enum": [ @@ -4548,6 +5713,21 @@ "type": "string", "description": "User-agent of the browser", "nullable": true + }, + "os_type": { + "type": "string", + "description": "The os type of the client device", + "nullable": true + }, + "os_version": { + "type": "string", + "description": "The os version of the client device", + "nullable": true + }, + "device_model": { + "type": "string", + "description": "The device model of the client", + "nullable": true } } }, @@ -4631,6 +5811,11 @@ "description": "A list of allowed domains (glob patterns) where this link can be embedded / opened from", "uniqueItems": true, "nullable": true + }, + "branding_visibility": { + "type": "boolean", + "description": "Toggle for HyperSwitch branding visibility", + "nullable": true } } } @@ -4669,7 +5854,8 @@ "automatic", "manual", "manual_multiple", - "scheduled" + "scheduled", + "sequential_automatic" ] }, "CaptureResponse": { @@ -4900,7 +6086,8 @@ "card_number", "card_exp_month", "card_exp_year", - "card_holder_name" + "card_holder_name", + "card_issuing_country" ], "properties": { "card_number": { @@ -4930,9 +6117,7 @@ "nullable": true }, "card_issuing_country": { - "type": "string", - "description": "Card Issuing Country", - "nullable": true + "$ref": "#/components/schemas/CountryAlpha2" }, "card_network": { "allOf": [ @@ -4948,8 +6133,11 @@ "nullable": true }, "card_type": { - "type": "string", - "description": "Card Type", + "allOf": [ + { + "$ref": "#/components/schemas/CardType" + } + ], "nullable": true } }, @@ -4961,12 +6149,12 @@ "saved_to_locker" ], "properties": { - "scheme": { - "type": "string", - "nullable": true - }, "issuer_country": { - "type": "string", + "allOf": [ + { + "$ref": "#/components/schemas/CountryAlpha2" + } + ], "nullable": true }, "last4_digits": { @@ -4981,10 +6169,6 @@ "type": "string", "nullable": true }, - "card_token": { - "type": "string", - "nullable": true - }, "card_holder_name": { "type": "string", "nullable": true @@ -5025,21 +6209,9 @@ "CardDetailUpdate": { "type": "object", "required": [ - "card_exp_month", - "card_exp_year", "card_holder_name" ], "properties": { - "card_exp_month": { - "type": "string", - "description": "Card Expiry Month", - "example": "10" - }, - "card_exp_year": { - "type": "string", - "description": "Card Expiry Year", - "example": "25" - }, "card_holder_name": { "type": "string", "description": "Card Holder Name", @@ -5071,6 +6243,41 @@ "Maestro" ] }, + "CardNetworkTypes": { + "type": "object", + "required": [ + "eligible_connectors" + ], + "properties": { + "card_network": { + "allOf": [ + { + "$ref": "#/components/schemas/CardNetwork" + } + ], + "nullable": true + }, + "surcharge_details": { + "allOf": [ + { + "$ref": "#/components/schemas/SurchargeDetailsResponse" + } + ], + "nullable": true + }, + "eligible_connectors": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The list of eligible connectors for a given card network", + "example": [ + "stripe", + "adyen" + ] + } + } + }, "CardPayout": { "type": "object", "required": [ @@ -5264,6 +6471,13 @@ } ] }, + "CardType": { + "type": "string", + "enum": [ + "credit", + "debit" + ] + }, "CashappQr": { "type": "object" }, @@ -5290,6 +6504,73 @@ } } }, + "ClickToPaySessionResponse": { + "type": "object", + "required": [ + "dpa_id", + "dpa_name", + "locale", + "card_brands", + "acquirer_bin", + "acquirer_merchant_id", + "merchant_category_code", + "merchant_country_code", + "transaction_amount", + "transaction_currency_code" + ], + "properties": { + "dpa_id": { + "type": "string" + }, + "dpa_name": { + "type": "string" + }, + "locale": { + "type": "string" + }, + "card_brands": { + "type": "array", + "items": { + "type": "string" + } + }, + "acquirer_bin": { + "type": "string" + }, + "acquirer_merchant_id": { + "type": "string" + }, + "merchant_category_code": { + "type": "string" + }, + "merchant_country_code": { + "type": "string" + }, + "transaction_amount": { + "type": "string", + "example": "38.02" + }, + "transaction_currency_code": { + "$ref": "#/components/schemas/Currency" + }, + "phone_number": { + "type": "string", + "example": "9123456789", + "nullable": true, + "maxLength": 255 + }, + "email": { + "type": "string", + "example": "johntest@test.com", + "nullable": true, + "maxLength": 255 + }, + "phone_country_code": { + "type": "string", + "nullable": true + } + } + }, "Comparison": { "type": "object", "description": "Represents a single comparison condition.", @@ -5357,11 +6638,14 @@ "checkout", "coinbase", "cryptopay", + "ctp_mastercard", "cybersource", "datatrans", "deutschebank", + "digitalvirgo", "dlocal", "ebanx", + "elavon", "fiserv", "fiservemea", "fiuu", @@ -5373,12 +6657,14 @@ "helcim", "iatapay", "itaubank", + "jpmorgan", "klarna", "mifinity", "mollie", "multisafepay", "netcetera", "nexinets", + "nexixpay", "nmi", "noon", "novalnet", @@ -5414,6 +6700,43 @@ "zsl" ] }, + "ConnectorFeatureMatrixResponse": { + "type": "object", + "required": [ + "name", + "supported_payment_methods" + ], + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "category": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentConnectorCategory" + } + ], + "nullable": true + }, + "supported_payment_methods": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SupportedPaymentMethod" + } + }, + "supported_webhook_flows": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EventClass" + }, + "nullable": true + } + } + }, "ConnectorMetadata": { "type": "object", "description": "Some connectors like Apple Pay, Airwallex and Noon might require some additional information, find specific details in the child attributes below.", @@ -5551,6 +6874,11 @@ "type": "object", "description": "This field contains the Samsung Pay certificates and credentials", "nullable": true + }, + "paze": { + "type": "object", + "description": "This field contains the Paze certificates and credentials", + "nullable": true } }, "additionalProperties": false @@ -5917,11 +7245,37 @@ } ] }, + "CtpServiceDetails": { + "type": "object", + "properties": { + "merchant_transaction_id": { + "type": "string", + "description": "merchant transaction id", + "nullable": true + }, + "correlation_id": { + "type": "string", + "description": "network transaction correlation id", + "nullable": true + }, + "x_src_flow_id": { + "type": "string", + "description": "session transaction flow id", + "nullable": true + }, + "provider": { + "type": "string", + "description": "provider Eg: Visa, Mastercard", + "nullable": true + } + } + }, "Currency": { "type": "string", "description": "The three letter ISO currency code in uppercase. Eg: 'USD' for the United States Dollar.", "enum": [ "AED", + "AFN", "ALL", "AMD", "ANG", @@ -5941,10 +7295,12 @@ "BOB", "BRL", "BSD", + "BTN", "BWP", "BYN", "BZD", "CAD", + "CDF", "CHF", "CLP", "CNY", @@ -5958,6 +7314,7 @@ "DOP", "DZD", "EGP", + "ERN", "ETB", "EUR", "FJD", @@ -5979,6 +7336,8 @@ "ILS", "INR", "IQD", + "IRR", + "ISK", "JMD", "JOD", "JPY", @@ -5986,6 +7345,7 @@ "KGS", "KHR", "KMF", + "KPW", "KRW", "KWD", "KYD", @@ -6032,6 +7392,7 @@ "SAR", "SBD", "SCR", + "SDG", "SEK", "SGD", "SHP", @@ -6042,8 +7403,11 @@ "SSP", "STN", "SVC", + "SYP", "SZL", "THB", + "TJS", + "TMT", "TND", "TOP", "TRY", @@ -6065,7 +7429,8 @@ "XPF", "YER", "ZAR", - "ZMW" + "ZMW", + "ZWL" ] }, "CustomerAcceptance": { @@ -6132,13 +7497,20 @@ "CustomerDeleteResponse": { "type": "object", "required": [ + "id", "merchant_reference_id", "customer_deleted", "address_deleted", - "payment_methods_deleted", - "id" + "payment_methods_deleted" ], "properties": { + "id": { + "type": "string", + "description": "Unique identifier for the customer", + "example": "12345_cus_01926c58bc6e77c09e809964e72af8c8", + "maxLength": 64, + "minLength": 32 + }, "merchant_reference_id": { "type": "string", "description": "The identifier for the customer object", @@ -6159,10 +7531,6 @@ "type": "boolean", "description": "Whether payment methods deleted or not", "example": false - }, - "id": { - "type": "string", - "description": "Global id" } } }, @@ -6255,20 +7623,20 @@ "CustomerPaymentMethod": { "type": "object", "required": [ - "payment_token", "payment_method_id", "customer_id", - "payment_method", + "payment_method_type", "recurring_enabled", - "installment_payment_enabled", + "created", "requires_cvv", - "default_payment_method_set" + "is_default" ], "properties": { "payment_token": { "type": "string", "description": "Token for payment method in temporary card locker which gets refreshed often", - "example": "7ebf443f-a050-4067-84e5-e6f6d4800aef" + "example": "7ebf443f-a050-4067-84e5-e6f6d4800aef", + "nullable": true }, "payment_method_id": { "type": "string", @@ -6278,14 +7646,14 @@ "customer_id": { "type": "string", "description": "The unique identifier of the customer.", - "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", + "example": "12345_cus_01926c58bc6e77c09e809964e72af8c8", "maxLength": 64, - "minLength": 1 + "minLength": 32 }, - "payment_method": { + "payment_method_type": { "$ref": "#/components/schemas/PaymentMethod" }, - "payment_method_type": { + "payment_method_subtype": { "allOf": [ { "$ref": "#/components/schemas/PaymentMethodType" @@ -6293,65 +7661,15 @@ ], "nullable": true }, - "payment_method_issuer": { - "type": "string", - "description": "The name of the bank/ provider issuing the payment method to the end user", - "example": "Citibank", - "nullable": true - }, - "payment_method_issuer_code": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodIssuerCode" - } - ], - "nullable": true - }, "recurring_enabled": { "type": "boolean", "description": "Indicates whether the payment method is eligible for recurring payments", "example": true }, - "installment_payment_enabled": { - "type": "boolean", - "description": "Indicates whether the payment method is eligible for installment payments", - "example": true - }, - "payment_experience": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PaymentExperience" - }, - "description": "Type of payment experience enabled with the connector", - "example": [ - "redirect_to_url" - ], - "nullable": true - }, - "card": { - "allOf": [ - { - "$ref": "#/components/schemas/CardDetailFromLocker" - } - ], - "nullable": true - }, - "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. Metadata is useful for storing additional, structured information on an object.", - "nullable": true - }, - "created": { - "type": "string", - "format": "date-time", - "description": "A timestamp (ISO 8601 code) that determines when the customer was created", - "example": "2023-01-18T11:04:09.922Z", - "nullable": true - }, - "bank_transfer": { + "payment_method_data": { "allOf": [ { - "$ref": "#/components/schemas/Bank" + "$ref": "#/components/schemas/PaymentMethodListData" } ], "nullable": true @@ -6364,6 +7682,12 @@ ], "nullable": true }, + "created": { + "type": "string", + "format": "date-time", + "description": "A timestamp (ISO 8601 code) that determines when the payment method was created", + "example": "2023-01-18T11:04:09.922Z" + }, "surcharge_details": { "allOf": [ { @@ -6384,7 +7708,7 @@ "example": "2024-02-24T11:04:09.922Z", "nullable": true }, - "default_payment_method_set": { + "is_default": { "type": "boolean", "description": "Indicates if the payment method has been set to default or not", "example": true @@ -6463,8 +7787,89 @@ }, "phone_country_code": { "type": "string", - "description": "The country code for the customer phone number", - "example": "+65", + "description": "The country code for the customer phone number", + "example": "+65", + "nullable": true, + "maxLength": 255 + }, + "default_billing_address": { + "allOf": [ + { + "$ref": "#/components/schemas/AddressDetails" + } + ], + "nullable": true + }, + "default_shipping_address": { + "allOf": [ + { + "$ref": "#/components/schemas/AddressDetails" + } + ], + "nullable": true + }, + "metadata": { + "type": "object", + "description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500\ncharacters long. Metadata is useful for storing additional, structured information on an\nobject.", + "nullable": true + } + }, + "additionalProperties": false + }, + "CustomerResponse": { + "type": "object", + "required": [ + "id", + "merchant_reference_id", + "created_at" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for the customer", + "example": "12345_cus_01926c58bc6e77c09e809964e72af8c8", + "maxLength": 64, + "minLength": 32 + }, + "merchant_reference_id": { + "type": "string", + "description": "The identifier for the customer object", + "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", + "maxLength": 64, + "minLength": 1 + }, + "name": { + "type": "string", + "description": "The customer's name", + "example": "Jon Test", + "nullable": true, + "maxLength": 255 + }, + "email": { + "type": "string", + "description": "The customer's email address", + "example": "JonTest@test.com", + "nullable": true, + "maxLength": 255 + }, + "phone": { + "type": "string", + "description": "The customer's phone number", + "example": "9123456789", + "nullable": true, + "maxLength": 255 + }, + "phone_country_code": { + "type": "string", + "description": "The country code for the customer phone number", + "example": "+65", + "nullable": true, + "maxLength": 255 + }, + "description": { + "type": "string", + "description": "An arbitrary string that you can attach to a customer object.", + "example": "First Customer", "nullable": true, "maxLength": 255 }, @@ -6484,25 +7889,38 @@ ], "nullable": true }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "A timestamp (ISO 8601 code) that determines when the customer was created", + "example": "2023-01-18T11:04:09.922Z" + }, "metadata": { "type": "object", "description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500\ncharacters long. Metadata is useful for storing additional, structured information on an\nobject.", "nullable": true + }, + "default_payment_method_id": { + "type": "string", + "description": "The identifier for the default payment method.", + "example": "pm_djh2837dwduh890123", + "nullable": true, + "maxLength": 64 } } }, - "CustomerResponse": { + "CustomerUpdateRequest": { "type": "object", "required": [ - "merchant_reference_id", - "created_at", - "id" + "name", + "email" ], "properties": { "merchant_reference_id": { "type": "string", - "description": "The identifier for the customer object", + "description": "The merchant identifier for the customer object.", "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", + "nullable": true, "maxLength": 64, "minLength": 1 }, @@ -6510,14 +7928,12 @@ "type": "string", "description": "The customer's name", "example": "Jon Test", - "nullable": true, "maxLength": 255 }, "email": { "type": "string", "description": "The customer's email address", "example": "JonTest@test.com", - "nullable": true, "maxLength": 255 }, "phone": { @@ -6527,17 +7943,17 @@ "nullable": true, "maxLength": 255 }, - "phone_country_code": { + "description": { "type": "string", - "description": "The country code for the customer phone number", - "example": "+65", + "description": "An arbitrary string that you can attach to a customer object.", + "example": "First Customer", "nullable": true, "maxLength": 255 }, - "description": { + "phone_country_code": { "type": "string", - "description": "An arbitrary string that you can attach to a customer object.", - "example": "First Customer", + "description": "The country code for the customer phone number", + "example": "+65", "nullable": true, "maxLength": 255 }, @@ -6557,12 +7973,6 @@ ], "nullable": true }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "A timestamp (ISO 8601 code) that determines when the customer was created", - "example": "2023-01-18T11:04:09.922Z" - }, "metadata": { "type": "object", "description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500\ncharacters long. Metadata is useful for storing additional, structured information on an\nobject.", @@ -6570,16 +7980,12 @@ }, "default_payment_method_id": { "type": "string", - "description": "The identifier for the default payment method.", - "example": "pm_djh2837dwduh890123", - "nullable": true, - "maxLength": 64 - }, - "id": { - "type": "string", - "description": "Global id" + "description": "The unique identifier of the payment method", + "example": "card_rGK4Vi5iSW70MY7J2mIg", + "nullable": true } - } + }, + "additionalProperties": false }, "DecoupledAuthenticationType": { "type": "string", @@ -6669,8 +8075,7 @@ "description": "The dispute amount" }, "currency": { - "type": "string", - "description": "The three-letter ISO currency code" + "$ref": "#/components/schemas/Currency" }, "dispute_stage": { "$ref": "#/components/schemas/DisputeStage" @@ -6865,6 +8270,61 @@ } } }, + "ElementPosition": { + "type": "string", + "enum": [ + "left", + "top left", + "top", + "top right", + "right", + "bottom right", + "bottom", + "bottom left", + "center" + ] + }, + "ElementSize": { + "oneOf": [ + { + "type": "object", + "required": [ + "Variants" + ], + "properties": { + "Variants": { + "$ref": "#/components/schemas/SizeVariants" + } + } + }, + { + "type": "object", + "required": [ + "Percentage" + ], + "properties": { + "Percentage": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + } + }, + { + "type": "object", + "required": [ + "Pixels" + ], + "properties": { + "Pixels": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + } + } + ] + }, "EnablePaymentLinkRequest": { "type": "string", "description": "Whether payment link is requested to be enabled or not for this transaction", @@ -6927,6 +8387,44 @@ } } }, + "ErrorCategory": { + "type": "string", + "enum": [ + "frm_decline", + "processor_downtime", + "processor_decline_unauthorized", + "issue_with_payment_method", + "processor_decline_incorrect_data" + ] + }, + "ErrorDetails": { + "type": "object", + "description": "Error details for the payment", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "The error code" + }, + "message": { + "type": "string", + "description": "The error message" + }, + "unified_code": { + "type": "string", + "description": "The unified error code across all connectors.\nThis can be relied upon for taking decisions based on the error.", + "nullable": true + }, + "unified_message": { + "type": "string", + "description": "The unified error message across all connectors.\nIf there is a translation available, this will have the translated message", + "nullable": true + } + } + }, "EventClass": { "type": "string", "enum": [ @@ -7211,6 +8709,38 @@ } } }, + "FeatureMatrixListResponse": { + "type": "object", + "required": [ + "connector_count", + "connectors" + ], + "properties": { + "connector_count": { + "type": "integer", + "description": "The number of connectors included in the response", + "minimum": 0 + }, + "connectors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ConnectorFeatureMatrixResponse" + } + } + } + }, + "FeatureMatrixRequest": { + "type": "object", + "properties": { + "connectors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Connector" + }, + "nullable": true + } + } + }, "FeatureMetadata": { "type": "object", "description": "additional data that might be required by hyperswitch", @@ -7230,9 +8760,25 @@ }, "description": "Additional tags to be used for global search", "nullable": true + }, + "apple_pay_recurring_details": { + "allOf": [ + { + "$ref": "#/components/schemas/ApplePayRecurringDetails" + } + ], + "nullable": true } } }, + "FeatureStatus": { + "type": "string", + "description": "The status of the feature", + "enum": [ + "not_supported", + "supported" + ] + }, "FieldType": { "oneOf": [ { @@ -7556,18 +9102,49 @@ { "type": "string", "enum": [ - "browser_language" + "user_bsb_number" ] }, { "type": "string", "enum": [ - "browser_ip" + "user_bank_sort_code" + ] + }, + { + "type": "string", + "enum": [ + "user_bank_routing_number" + ] + }, + { + "type": "string", + "enum": [ + "user_msisdn" + ] + }, + { + "type": "string", + "enum": [ + "user_client_identifier" + ] + }, + { + "type": "string", + "enum": [ + "order_details_product_name" ] } ], "description": "Possible field type of required fields in payment_method_data" }, + "ForceSync": { + "type": "string", + "enum": [ + "true", + "false" + ] + }, "FrmAction": { "type": "string", "enum": [ @@ -8272,6 +9849,14 @@ "type": "string", "description": "error message unified across the connectors", "nullable": true + }, + "error_category": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorCategory" + } + ], + "nullable": true } } }, @@ -8405,6 +9990,14 @@ "type": "string", "description": "error message unified across the connectors", "nullable": true + }, + "error_category": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorCategory" + } + ], + "nullable": true } } }, @@ -8501,6 +10094,14 @@ "type": "string", "description": "error message unified across the connectors", "nullable": true + }, + "error_category": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorCategory" + } + ], + "nullable": true } } }, @@ -8986,8 +10587,7 @@ "MerchantAccountCreate": { "type": "object", "required": [ - "merchant_name", - "organization_id" + "merchant_name" ], "properties": { "merchant_name": { @@ -9008,13 +10608,6 @@ "type": "object", "description": "Metadata is useful for storing additional, unstructured information about the merchant account.", "nullable": true - }, - "organization_id": { - "type": "string", - "description": "The id of the organization to which the merchant belongs to. Please use the organization endpoint to create an organization", - "example": "org_q98uSGAYbjEwqs0mJwnz", - "maxLength": 64, - "minLength": 1 } }, "additionalProperties": false @@ -9183,7 +10776,8 @@ "required": [ "connector_type", "connector_name", - "profile_id" + "profile_id", + "payment_methods_enabled" ], "properties": { "connector_type": { @@ -9212,48 +10806,7 @@ "nullable": true }, "payment_methods_enabled": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PaymentMethodsEnabled" - }, - "description": "An object containing the details about the payment methods that need to be enabled under this merchant connector account", - "example": [ - { - "accepted_countries": { - "list": [ - "FR", - "DE", - "IN" - ], - "type": "disable_only" - }, - "accepted_currencies": { - "list": [ - "USD", - "EUR" - ], - "type": "enable_only" - }, - "installment_payment_enabled": true, - "maximum_amount": 68607706, - "minimum_amount": 1, - "payment_method": "wallet", - "payment_method_issuers": [ - "labore magna ipsum", - "aute" - ], - "payment_method_types": [ - "upi_collect", - "upi_intent" - ], - "payment_schemes": [ - "Discover", - "Discover" - ], - "recurring_enabled": true - } - ], - "nullable": true + "$ref": "#/components/schemas/PaymentMethodsEnabled" }, "connector_webhook_details": { "allOf": [ @@ -9396,6 +10949,7 @@ "connector_name", "id", "profile_id", + "payment_methods_enabled", "status" ], "properties": { @@ -9426,49 +10980,7 @@ "items": { "$ref": "#/components/schemas/PaymentMethodsEnabled" }, - "description": "An object containing the details about the payment methods that need to be enabled under this merchant connector account", - "example": [ - { - "accepted_countries": { - "list": [ - "FR", - "DE", - "IN" - ], - "type": "disable_only" - }, - "accepted_currencies": { - "list": [ - "USD", - "EUR" - ], - "type": "enable_only" - }, - "installment_payment_enabled": true, - "maximum_amount": 68607706, - "minimum_amount": 1, - "payment_method": "wallet", - "payment_method_issuers": [ - "labore magna ipsum", - "aute" - ], - "payment_method_types": [ - "upi_collect", - "upi_intent" - ], - "payment_schemes": [ - "Discover", - "Discover" - ], - "recurring_enabled": true - } - ], - "nullable": true - }, - "metadata": { - "type": "object", - "description": "Metadata is useful for storing additional, unstructured information on an object.", - "nullable": true + "description": "An object containing the details about the payment methods that need to be enabled under this merchant connector account" }, "disabled": { "type": "boolean", @@ -9495,27 +11007,11 @@ "nullable": true }, "pm_auth_config": { - "type": "object", - "nullable": true - }, - "status": { - "$ref": "#/components/schemas/ConnectorStatus" - }, - "additional_merchant_data": { - "allOf": [ - { - "$ref": "#/components/schemas/AdditionalMerchantData" - } - ], + "type": "object", "nullable": true }, - "connector_wallets_details": { - "allOf": [ - { - "$ref": "#/components/schemas/ConnectorWalletDetails" - } - ], - "nullable": true + "status": { + "$ref": "#/components/schemas/ConnectorStatus" } }, "additionalProperties": false @@ -9528,6 +11024,7 @@ "connector_name", "id", "profile_id", + "payment_methods_enabled", "status" ], "properties": { @@ -9566,44 +11063,7 @@ "items": { "$ref": "#/components/schemas/PaymentMethodsEnabled" }, - "description": "An object containing the details about the payment methods that need to be enabled under this merchant connector account", - "example": [ - { - "accepted_countries": { - "list": [ - "FR", - "DE", - "IN" - ], - "type": "disable_only" - }, - "accepted_currencies": { - "list": [ - "USD", - "EUR" - ], - "type": "enable_only" - }, - "installment_payment_enabled": true, - "maximum_amount": 68607706, - "minimum_amount": 1, - "payment_method": "wallet", - "payment_method_issuers": [ - "labore magna ipsum", - "aute" - ], - "payment_method_types": [ - "upi_collect", - "upi_intent" - ], - "payment_schemes": [ - "Discover", - "Discover" - ], - "recurring_enabled": true - } - ], - "nullable": true + "description": "An object containing the details about the payment methods that need to be enabled under this merchant connector account" }, "connector_webhook_details": { "allOf": [ @@ -9701,42 +11161,6 @@ "$ref": "#/components/schemas/PaymentMethodsEnabled" }, "description": "An object containing the details about the payment methods that need to be enabled under this merchant connector account", - "example": [ - { - "accepted_countries": { - "list": [ - "FR", - "DE", - "IN" - ], - "type": "disable_only" - }, - "accepted_currencies": { - "list": [ - "USD", - "EUR" - ], - "type": "enable_only" - }, - "installment_payment_enabled": true, - "maximum_amount": 68607706, - "minimum_amount": 1, - "payment_method": "wallet", - "payment_method_issuers": [ - "labore magna ipsum", - "aute" - ], - "payment_method_types": [ - "upi_collect", - "upi_intent" - ], - "payment_schemes": [ - "Discover", - "Discover" - ], - "recurring_enabled": true - } - ], "nullable": true }, "connector_webhook_details": { @@ -10017,6 +11441,71 @@ "MobilePayRedirection": { "type": "object" }, + "MobilePaymentConsent": { + "type": "string", + "enum": [ + "consent_required", + "consent_not_required", + "consent_optional" + ] + }, + "MobilePaymentData": { + "oneOf": [ + { + "type": "object", + "required": [ + "direct_carrier_billing" + ], + "properties": { + "direct_carrier_billing": { + "type": "object", + "required": [ + "msisdn" + ], + "properties": { + "msisdn": { + "type": "string", + "description": "The phone number of the user", + "example": "1234567890" + }, + "client_uid": { + "type": "string", + "description": "Unique user id", + "example": "02iacdYXGI9CnyJdoN8c7", + "nullable": true + } + } + } + } + } + ] + }, + "MobilePaymentNextStepData": { + "type": "object", + "required": [ + "consent_data_required" + ], + "properties": { + "consent_data_required": { + "$ref": "#/components/schemas/MobilePaymentConsent" + } + } + }, + "MobilePaymentResponse": { + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/MobilePaymentData" + } + ], + "nullable": true + }, + { + "type": "object" + } + ] + }, "MomoRedirection": { "type": "object" }, @@ -10047,9 +11536,81 @@ } } }, + "NetworkTransactionIdAndCardDetails": { + "type": "object", + "required": [ + "card_number", + "card_exp_month", + "card_exp_year", + "card_holder_name", + "network_transaction_id" + ], + "properties": { + "card_number": { + "type": "string", + "description": "The card number", + "example": "4242424242424242" + }, + "card_exp_month": { + "type": "string", + "description": "The card's expiry month", + "example": "24" + }, + "card_exp_year": { + "type": "string", + "description": "The card's expiry year", + "example": "24" + }, + "card_holder_name": { + "type": "string", + "description": "The card holder's name", + "example": "John Test" + }, + "card_issuer": { + "type": "string", + "description": "The name of the issuer of card", + "example": "chase", + "nullable": true + }, + "card_network": { + "allOf": [ + { + "$ref": "#/components/schemas/CardNetwork" + } + ], + "nullable": true + }, + "card_type": { + "type": "string", + "example": "CREDIT", + "nullable": true + }, + "card_issuing_country": { + "type": "string", + "example": "INDIA", + "nullable": true + }, + "bank_code": { + "type": "string", + "example": "JP_AMEX", + "nullable": true + }, + "nick_name": { + "type": "string", + "description": "The card holder's nick name", + "example": "John Test", + "nullable": true + }, + "network_transaction_id": { + "type": "string", + "description": "The network transaction ID provided by the card network during a CIT (Customer Initiated Transaction),\nwhere `setup_future_usage` is set to `off_session`." + } + } + }, "NextActionCall": { "type": "string", "enum": [ + "post_session_tokens", "confirm", "sync", "complete_authorize" @@ -10245,6 +11806,25 @@ ] } } + }, + { + "type": "object", + "description": "Contains consent to collect otp for mobile payment", + "required": [ + "consent_data_required", + "type" + ], + "properties": { + "consent_data_required": { + "$ref": "#/components/schemas/MobilePaymentConsent" + }, + "type": { + "type": "string", + "enum": [ + "collect_otp" + ] + } + } } ], "discriminator": { @@ -10259,7 +11839,8 @@ "invoke_sdk_client", "trigger_api", "display_bank_transfer_information", - "display_wait_screen" + "display_wait_screen", + "collect_otp" ] }, "NoThirdPartySdkSessionResponse": { @@ -10417,70 +11998,6 @@ } } }, - "OrderDetails": { - "type": "object", - "required": [ - "product_name", - "quantity" - ], - "properties": { - "product_name": { - "type": "string", - "description": "Name of the product that is being purchased", - "example": "shirt", - "maxLength": 255 - }, - "quantity": { - "type": "integer", - "format": "int32", - "description": "The quantity of the product to be purchased", - "example": 1, - "minimum": 0 - }, - "requires_shipping": { - "type": "boolean", - "nullable": true - }, - "product_img_link": { - "type": "string", - "description": "The image URL of the product", - "nullable": true - }, - "product_id": { - "type": "string", - "description": "ID of the product that is being purchased", - "nullable": true - }, - "category": { - "type": "string", - "description": "Category of the product that is being purchased", - "nullable": true - }, - "sub_category": { - "type": "string", - "description": "Sub category of the product that is being purchased", - "nullable": true - }, - "brand": { - "type": "string", - "description": "Brand of the product that is being purchased", - "nullable": true - }, - "product_type": { - "allOf": [ - { - "$ref": "#/components/schemas/ProductType" - } - ], - "nullable": true - }, - "product_tax_code": { - "type": "string", - "description": "The tax code for the product", - "nullable": true - } - } - }, "OrderDetailsWithAmount": { "type": "object", "required": [ @@ -10507,6 +12024,18 @@ "format": "int64", "description": "the amount per quantity of product" }, + "tax_rate": { + "type": "number", + "format": "double", + "description": "tax rate applicable to the product", + "nullable": true + }, + "total_tax_amount": { + "type": "integer", + "format": "int64", + "description": "total tax amount applicable to the product", + "nullable": true + }, "requires_shipping": { "type": "boolean", "nullable": true @@ -10565,14 +12094,17 @@ ], "properties": { "organization_name": { - "type": "string" + "type": "string", + "description": "Name of the organization" }, "organization_details": { "type": "object", + "description": "Details about the organization", "nullable": true }, "metadata": { "type": "object", + "description": "Metadata is useful for storing additional, unstructured information on an object.", "nullable": true } }, @@ -10581,27 +12113,31 @@ "OrganizationResponse": { "type": "object", "required": [ - "organization_id", + "id", "modified_at", "created_at" ], "properties": { - "organization_id": { + "id": { "type": "string", + "description": "The unique identifier for the Organization", "example": "org_q98uSGAYbjEwqs0mJwnz", "maxLength": 64, "minLength": 1 }, "organization_name": { "type": "string", + "description": "Name of the Organization", "nullable": true }, "organization_details": { "type": "object", + "description": "Details about the organization", "nullable": true }, "metadata": { "type": "object", + "description": "Metadata is useful for storing additional, unstructured information on an object.", "nullable": true }, "modified_at": { @@ -10619,14 +12155,17 @@ "properties": { "organization_name": { "type": "string", + "description": "Name of the organization", "nullable": true }, "organization_details": { "type": "object", + "description": "Details about the organization", "nullable": true }, "metadata": { "type": "object", + "description": "Metadata is useful for storing additional, unstructured information on an object.", "nullable": true } }, @@ -10908,6 +12447,17 @@ } } }, + { + "type": "object", + "required": [ + "klarna_checkout" + ], + "properties": { + "klarna_checkout": { + "type": "object" + } + } + }, { "type": "object", "required": [ @@ -11005,13 +12555,95 @@ } } }, - "PaylaterResponse": { + "PaylaterResponse": { + "type": "object", + "properties": { + "klarna_sdk": { + "allOf": [ + { + "$ref": "#/components/schemas/KlarnaSdkPaymentMethodResponse" + } + ], + "nullable": true + } + } + }, + "PaymentAmountDetailsResponse": { "type": "object", + "required": [ + "currency", + "external_tax_calculation", + "surcharge_calculation", + "net_amount", + "amount_capturable" + ], "properties": { - "klarna_sdk": { + "order_amount": { + "type": "integer", + "format": "int64", + "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies)", + "example": 6540, + "minimum": 0 + }, + "currency": { + "$ref": "#/components/schemas/Currency" + }, + "shipping_cost": { "allOf": [ { - "$ref": "#/components/schemas/KlarnaSdkPaymentMethodResponse" + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "order_tax_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "external_tax_calculation": { + "$ref": "#/components/schemas/TaxCalculationOverride" + }, + "surcharge_calculation": { + "$ref": "#/components/schemas/SurchargeCalculationOverride" + }, + "surcharge_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "tax_on_surcharge": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "net_amount": { + "$ref": "#/components/schemas/MinorUnit" + }, + "amount_to_capture": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "amount_capturable": { + "$ref": "#/components/schemas/MinorUnit" + }, + "amount_captured": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" } ], "nullable": true @@ -11041,6 +12673,13 @@ "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 }, + "order_tax_amount": { + "type": "integer", + "format": "int64", + "description": "The payment attempt tax_amount.", + "example": 6540, + "nullable": true + }, "currency": { "allOf": [ { @@ -11169,59 +12808,6 @@ } } }, - "PaymentChargeRequest": { - "type": "object", - "description": "Fee information to be charged on the payment being collected", - "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", - "example": 6540 - }, - "transfer_account_id": { - "type": "string", - "description": "Identifier for the reseller's account to send the funds to" - } - } - }, - "PaymentChargeResponse": { - "type": "object", - "description": "Fee information to be charged on the payment being collected", - "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", - "example": 6540 - }, - "transfer_account_id": { - "type": "string", - "description": "Identifier for the reseller's account where the funds were transferred" - } - } - }, "PaymentChargeType": { "oneOf": [ { @@ -11237,6 +12823,15 @@ } ] }, + "PaymentConnectorCategory": { + "type": "string", + "description": "Connector Access Method", + "enum": [ + "payment_gateway", + "alternative_payment_method", + "bank_acquirer" + ] + }, "PaymentCreatePaymentLinkConfig": { "allOf": [ { @@ -11263,9 +12858,66 @@ "one_click", "link_wallet", "invoke_payment_app", - "display_wait_screen" + "display_wait_screen", + "collect_otp" ] }, + "PaymentExperienceTypes": { + "type": "object", + "required": [ + "eligible_connectors" + ], + "properties": { + "payment_experience_type": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentExperience" + } + ], + "nullable": true + }, + "eligible_connectors": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The list of eligible connectors for a given payment experience", + "example": [ + "stripe", + "adyen" + ] + } + } + }, + "PaymentLinkBackgroundImageConfig": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string", + "description": "URL of the image", + "example": "https://hyperswitch.io/favicon.ico" + }, + "position": { + "allOf": [ + { + "$ref": "#/components/schemas/ElementPosition" + } + ], + "nullable": true + }, + "size": { + "allOf": [ + { + "$ref": "#/components/schemas/ElementSize" + } + ], + "nullable": true + } + } + }, "PaymentLinkConfig": { "type": "object", "required": [ @@ -11274,7 +12926,9 @@ "seller_name", "sdk_layout", "display_sdk_only", - "enabled_saved_payment_method" + "enabled_saved_payment_method", + "hide_card_nickname_field", + "show_card_form_by_default" ], "properties": { "theme": { @@ -11301,6 +12955,14 @@ "type": "boolean", "description": "Enable saved payment method option for payment link" }, + "hide_card_nickname_field": { + "type": "boolean", + "description": "Hide card nickname field option for payment link" + }, + "show_card_form_by_default": { + "type": "boolean", + "description": "Show card form by default for payment link" + }, "allowed_domains": { "type": "array", "items": { @@ -11317,6 +12979,32 @@ }, "description": "Dynamic details related to merchant to be rendered in payment link", "nullable": true + }, + "background_image": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentLinkBackgroundImageConfig" + } + ], + "nullable": true + }, + "details_layout": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentLinkDetailsLayout" + } + ], + "nullable": true + }, + "branding_visibility": { + "type": "boolean", + "description": "Toggle for HyperSwitch branding visibility", + "nullable": true + }, + "payment_button_text": { + "type": "string", + "description": "Text for payment link's handle confirm button", + "nullable": true } } }, @@ -11365,6 +13053,20 @@ "example": true, "nullable": true }, + "hide_card_nickname_field": { + "type": "boolean", + "description": "Hide card nickname field option for payment link", + "default": false, + "example": true, + "nullable": true + }, + "show_card_form_by_default": { + "type": "boolean", + "description": "Show card form by default for payment link", + "default": true, + "example": true, + "nullable": true + }, "transaction_details": { "type": "array", "items": { @@ -11372,9 +13074,37 @@ }, "description": "Dynamic details related to merchant to be rendered in payment link", "nullable": true + }, + "background_image": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentLinkBackgroundImageConfig" + } + ], + "nullable": true + }, + "details_layout": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentLinkDetailsLayout" + } + ], + "nullable": true + }, + "payment_button_text": { + "type": "string", + "description": "Text for payment link's handle confirm button", + "nullable": true } } }, + "PaymentLinkDetailsLayout": { + "type": "string", + "enum": [ + "layout1", + "layout2" + ] + }, "PaymentLinkInitiateRequest": { "type": "object", "required": [ @@ -11555,7 +13285,8 @@ "upi", "voucher", "gift_card", - "open_banking" + "open_banking", + "mobile_payment" ] }, "PaymentMethodCollectLinkRequest": { @@ -11669,41 +13400,17 @@ "PaymentMethodCreate": { "type": "object", "required": [ - "payment_method" - ], - "properties": { - "payment_method": { - "$ref": "#/components/schemas/PaymentMethod" - }, - "payment_method_type": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodType" - } - ], - "nullable": true - }, - "payment_method_issuer": { - "type": "string", - "description": "The name of the bank/ provider issuing the payment method to the end user", - "example": "Citibank", - "nullable": true - }, - "payment_method_issuer_code": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodIssuerCode" - } - ], - "nullable": true - }, - "card": { - "allOf": [ - { - "$ref": "#/components/schemas/CardDetail" - } - ], - "nullable": true + "payment_method_type", + "payment_method_subtype", + "customer_id", + "payment_method_data" + ], + "properties": { + "payment_method_type": { + "$ref": "#/components/schemas/PaymentMethod" + }, + "payment_method_subtype": { + "$ref": "#/components/schemas/PaymentMethodType" }, "metadata": { "type": "object", @@ -11713,45 +13420,12 @@ "customer_id": { "type": "string", "description": "The unique identifier of the customer.", - "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", - "nullable": true, + "example": "12345_cus_01926c58bc6e77c09e809964e72af8c8", "maxLength": 64, - "minLength": 1 - }, - "card_network": { - "type": "string", - "description": "The card network", - "example": "Visa", - "nullable": true - }, - "bank_transfer": { - "allOf": [ - { - "$ref": "#/components/schemas/Bank" - } - ], - "nullable": true - }, - "wallet": { - "allOf": [ - { - "$ref": "#/components/schemas/Wallet" - } - ], - "nullable": true - }, - "client_secret": { - "type": "string", - "description": "For Client based calls, SDK will use the client_secret\nin order to call /payment_methods\nClient secret will be generated whenever a new\npayment method is created", - "nullable": true + "minLength": 32 }, "payment_method_data": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodCreateData" - } - ], - "nullable": true + "$ref": "#/components/schemas/PaymentMethodCreateData" }, "billing": { "allOf": [ @@ -11831,924 +13505,447 @@ }, { "type": "object", - "title": "BankRedirect", - "required": [ - "bank_redirect" - ], - "properties": { - "bank_redirect": { - "$ref": "#/components/schemas/BankRedirectData" - } - } - }, - { - "type": "object", - "title": "BankDebit", - "required": [ - "bank_debit" - ], - "properties": { - "bank_debit": { - "$ref": "#/components/schemas/BankDebitData" - } - } - }, - { - "type": "object", - "title": "BankTransfer", - "required": [ - "bank_transfer" - ], - "properties": { - "bank_transfer": { - "$ref": "#/components/schemas/BankTransferData" - } - } - }, - { - "type": "object", - "title": "RealTimePayment", - "required": [ - "real_time_payment" - ], - "properties": { - "real_time_payment": { - "$ref": "#/components/schemas/RealTimePaymentData" - } - } - }, - { - "type": "object", - "title": "Crypto", - "required": [ - "crypto" - ], - "properties": { - "crypto": { - "$ref": "#/components/schemas/CryptoData" - } - } - }, - { - "type": "string", - "title": "MandatePayment", - "enum": [ - "mandate_payment" - ] - }, - { - "type": "string", - "title": "Reward", - "enum": [ - "reward" - ] - }, - { - "type": "object", - "title": "Upi", - "required": [ - "upi" - ], - "properties": { - "upi": { - "$ref": "#/components/schemas/UpiData" - } - } - }, - { - "type": "object", - "title": "Voucher", - "required": [ - "voucher" - ], - "properties": { - "voucher": { - "$ref": "#/components/schemas/VoucherData" - } - } - }, - { - "type": "object", - "title": "GiftCard", - "required": [ - "gift_card" - ], - "properties": { - "gift_card": { - "$ref": "#/components/schemas/GiftCardData" - } - } - }, - { - "type": "object", - "title": "CardToken", - "required": [ - "card_token" - ], - "properties": { - "card_token": { - "$ref": "#/components/schemas/CardToken" - } - } - }, - { - "type": "object", - "title": "OpenBanking", - "required": [ - "open_banking" - ], - "properties": { - "open_banking": { - "$ref": "#/components/schemas/OpenBankingData" - } - } - } - ] - }, - "PaymentMethodDataRequest": { - "allOf": [ - { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodData" - } - ], - "nullable": true - }, - { - "type": "object", - "properties": { - "billing": { - "allOf": [ - { - "$ref": "#/components/schemas/Address" - } - ], - "nullable": true - } - } - } - ], - "description": "The payment method information provided for making a payment" - }, - "PaymentMethodDataResponse": { - "oneOf": [ - { - "type": "object", - "required": [ - "card" - ], - "properties": { - "card": { - "$ref": "#/components/schemas/CardResponse" - } - } - }, - { - "type": "object", - "required": [ - "bank_transfer" - ], - "properties": { - "bank_transfer": { - "$ref": "#/components/schemas/BankTransferResponse" - } - } - }, - { - "type": "object", - "required": [ - "wallet" - ], - "properties": { - "wallet": { - "$ref": "#/components/schemas/WalletResponse" - } - } - }, - { - "type": "object", - "required": [ - "pay_later" - ], - "properties": { - "pay_later": { - "$ref": "#/components/schemas/PaylaterResponse" - } - } - }, - { - "type": "object", - "required": [ - "bank_redirect" - ], - "properties": { - "bank_redirect": { - "$ref": "#/components/schemas/BankRedirectResponse" - } - } - }, - { - "type": "object", + "title": "BankRedirect", "required": [ - "crypto" + "bank_redirect" ], "properties": { - "crypto": { - "$ref": "#/components/schemas/CryptoResponse" + "bank_redirect": { + "$ref": "#/components/schemas/BankRedirectData" } } }, { "type": "object", + "title": "BankDebit", "required": [ "bank_debit" ], "properties": { "bank_debit": { - "$ref": "#/components/schemas/BankDebitResponse" + "$ref": "#/components/schemas/BankDebitData" } } }, { "type": "object", + "title": "BankTransfer", "required": [ - "mandate_payment" + "bank_transfer" ], "properties": { - "mandate_payment": { - "type": "object" + "bank_transfer": { + "$ref": "#/components/schemas/BankTransferData" } } }, { "type": "object", + "title": "RealTimePayment", "required": [ - "reward" + "real_time_payment" ], "properties": { - "reward": { - "type": "object" + "real_time_payment": { + "$ref": "#/components/schemas/RealTimePaymentData" } } }, { "type": "object", + "title": "Crypto", "required": [ - "real_time_payment" + "crypto" ], "properties": { - "real_time_payment": { - "$ref": "#/components/schemas/RealTimePaymentDataResponse" + "crypto": { + "$ref": "#/components/schemas/CryptoData" } } }, + { + "type": "string", + "title": "MandatePayment", + "enum": [ + "mandate_payment" + ] + }, + { + "type": "string", + "title": "Reward", + "enum": [ + "reward" + ] + }, { "type": "object", + "title": "Upi", "required": [ "upi" ], "properties": { "upi": { - "$ref": "#/components/schemas/UpiResponse" + "$ref": "#/components/schemas/UpiData" } } }, { "type": "object", + "title": "Voucher", "required": [ "voucher" ], "properties": { "voucher": { - "$ref": "#/components/schemas/VoucherResponse" + "$ref": "#/components/schemas/VoucherData" } } }, { "type": "object", + "title": "GiftCard", "required": [ "gift_card" ], "properties": { "gift_card": { - "$ref": "#/components/schemas/GiftCardResponse" - } - } - }, - { - "type": "object", - "required": [ - "card_redirect" - ], - "properties": { - "card_redirect": { - "$ref": "#/components/schemas/CardRedirectResponse" + "$ref": "#/components/schemas/GiftCardData" } } }, { "type": "object", + "title": "CardToken", "required": [ "card_token" ], "properties": { "card_token": { - "$ref": "#/components/schemas/CardTokenResponse" + "$ref": "#/components/schemas/CardToken" } } }, { "type": "object", + "title": "OpenBanking", "required": [ "open_banking" ], "properties": { "open_banking": { - "$ref": "#/components/schemas/OpenBankingResponse" + "$ref": "#/components/schemas/OpenBankingData" } } - } - ] - }, - "PaymentMethodDataResponseWithBilling": { - "allOf": [ - { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodDataResponse" - } - ], - "nullable": true }, { "type": "object", + "title": "MobilePayment", + "required": [ + "mobile_payment" + ], "properties": { - "billing": { - "allOf": [ - { - "$ref": "#/components/schemas/Address" - } - ], - "nullable": true + "mobile_payment": { + "$ref": "#/components/schemas/MobilePaymentData" } } } ] }, - "PaymentMethodDeleteResponse": { - "type": "object", - "required": [ - "payment_method_id", - "deleted" - ], - "properties": { - "payment_method_id": { - "type": "string", - "description": "The unique identifier of the Payment method", - "example": "card_rGK4Vi5iSW70MY7J2mIg" - }, - "deleted": { - "type": "boolean", - "description": "Whether payment method was deleted or not", - "example": true - } - } - }, - "PaymentMethodIssuerCode": { - "type": "string", - "enum": [ - "jp_hdfc", - "jp_icici", - "jp_googlepay", - "jp_applepay", - "jp_phonepay", - "jp_wechat", - "jp_sofort", - "jp_giropay", - "jp_sepa", - "jp_bacs" - ] - }, - "PaymentMethodList": { - "type": "object", - "required": [ - "payment_method" - ], - "properties": { - "payment_method": { - "$ref": "#/components/schemas/PaymentMethod" - }, - "payment_method_types": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PaymentMethodType" - }, - "description": "This is a sub-category of payment method.", - "example": [ - "credit" - ], - "nullable": true - } - } - }, - "PaymentMethodListResponse": { - "type": "object", - "required": [ - "currency", - "payment_methods", - "mandate_payment", - "show_surcharge_breakup_screen", - "request_external_three_ds_authentication", - "is_tax_calculation_enabled" - ], - "properties": { - "redirect_url": { - "type": "string", - "description": "Redirect URL of the merchant", - "example": "https://www.google.com", - "nullable": true - }, - "currency": { - "$ref": "#/components/schemas/Currency" - }, - "payment_methods": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PaymentMethodList" - }, - "description": "Information about the payment method", - "example": [ - { - "payment_experience": null, - "payment_method": "wallet", - "payment_method_issuers": [ - "labore magna ipsum", - "aute" - ] - } - ] - }, - "mandate_payment": { - "$ref": "#/components/schemas/MandateType" - }, - "merchant_name": { - "type": "string", - "nullable": true - }, - "show_surcharge_breakup_screen": { - "type": "boolean", - "description": "flag to indicate if surcharge and tax breakup screen should be shown or not" - }, - "payment_type": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentType" - } - ], - "nullable": true - }, - "request_external_three_ds_authentication": { - "type": "boolean", - "description": "flag to indicate whether to perform external 3ds authentication", - "example": true - }, - "collect_shipping_details_from_wallets": { - "type": "boolean", - "description": "flag that indicates whether to collect shipping details from wallets or from the customer", - "nullable": true - }, - "collect_billing_details_from_wallets": { - "type": "boolean", - "description": "flag that indicates whether to collect billing details from wallets or from the customer", - "nullable": true - }, - "is_tax_calculation_enabled": { - "type": "boolean", - "description": "flag that indicates whether to calculate tax on the order amount" - } - } - }, - "PaymentMethodResponse": { - "type": "object", - "required": [ - "merchant_id", - "payment_method_id", - "payment_method", - "recurring_enabled", - "installment_payment_enabled" - ], - "properties": { - "merchant_id": { - "type": "string", - "description": "Unique identifier for a merchant", - "example": "merchant_1671528864" - }, - "customer_id": { - "type": "string", - "description": "The unique identifier of the customer.", - "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", - "nullable": true, - "maxLength": 64, - "minLength": 1 - }, - "payment_method_id": { - "type": "string", - "description": "The unique identifier of the Payment method", - "example": "card_rGK4Vi5iSW70MY7J2mIg" - }, - "payment_method": { - "$ref": "#/components/schemas/PaymentMethod" - }, - "payment_method_type": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodType" - } - ], - "nullable": true - }, - "card": { + "PaymentMethodDataRequest": { + "allOf": [ + { "allOf": [ { - "$ref": "#/components/schemas/CardDetailFromLocker" + "$ref": "#/components/schemas/PaymentMethodData" } ], "nullable": true }, - "recurring_enabled": { - "type": "boolean", - "description": "Indicates whether the payment method is eligible for recurring payments", - "example": true - }, - "installment_payment_enabled": { - "type": "boolean", - "description": "Indicates whether the payment method is eligible for installment payments", - "example": true + { + "type": "object", + "properties": { + "billing": { + "allOf": [ + { + "$ref": "#/components/schemas/Address" + } + ], + "nullable": true + } + } + } + ], + "description": "The payment method information provided for making a payment" + }, + "PaymentMethodDataResponse": { + "oneOf": [ + { + "type": "object", + "required": [ + "card" + ], + "properties": { + "card": { + "$ref": "#/components/schemas/CardResponse" + } + } }, - "payment_experience": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PaymentExperience" - }, - "description": "Type of payment experience enabled with the connector", - "example": [ - "redirect_to_url" + { + "type": "object", + "required": [ + "bank_transfer" ], - "nullable": true + "properties": { + "bank_transfer": { + "$ref": "#/components/schemas/BankTransferResponse" + } + } }, - "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. Metadata is useful for storing additional, structured information on an object.", - "nullable": true + "required": [ + "wallet" + ], + "properties": { + "wallet": { + "$ref": "#/components/schemas/WalletResponse" + } + } }, - "created": { - "type": "string", - "format": "date-time", - "description": "A timestamp (ISO 8601 code) that determines when the customer was created", - "example": "2023-01-18T11:04:09.922Z", - "nullable": true + { + "type": "object", + "required": [ + "pay_later" + ], + "properties": { + "pay_later": { + "$ref": "#/components/schemas/PaylaterResponse" + } + } }, - "bank_transfer": { - "allOf": [ - { - "$ref": "#/components/schemas/Bank" + { + "type": "object", + "required": [ + "bank_redirect" + ], + "properties": { + "bank_redirect": { + "$ref": "#/components/schemas/BankRedirectResponse" } + } + }, + { + "type": "object", + "required": [ + "crypto" ], - "nullable": true + "properties": { + "crypto": { + "$ref": "#/components/schemas/CryptoResponse" + } + } }, - "last_used_at": { - "type": "string", - "format": "date-time", - "example": "2024-02-24T11:04:09.922Z", - "nullable": true + { + "type": "object", + "required": [ + "bank_debit" + ], + "properties": { + "bank_debit": { + "$ref": "#/components/schemas/BankDebitResponse" + } + } }, - "client_secret": { - "type": "string", - "description": "For Client based calls", - "nullable": true - } - } - }, - "PaymentMethodStatus": { - "type": "string", - "description": "Payment Method Status", - "enum": [ - "active", - "inactive", - "processing", - "awaiting_data" - ] - }, - "PaymentMethodType": { - "type": "string", - "description": "Indicates the sub type of payment method. Eg: 'google_pay' & 'apple_pay' for wallets.", - "enum": [ - "ach", - "affirm", - "afterpay_clearpay", - "alfamart", - "ali_pay", - "ali_pay_hk", - "alma", - "apple_pay", - "atome", - "bacs", - "bancontact_card", - "becs", - "benefit", - "bizum", - "blik", - "boleto", - "bca_bank_transfer", - "bni_va", - "bri_va", - "card_redirect", - "cimb_va", - "classic", - "credit", - "crypto_currency", - "cashapp", - "dana", - "danamon_va", - "debit", - "duit_now", - "efecty", - "eps", - "fps", - "evoucher", - "giropay", - "givex", - "google_pay", - "go_pay", - "gcash", - "ideal", - "interac", - "indomaret", - "klarna", - "kakao_pay", - "local_bank_redirect", - "mandiri_va", - "knet", - "mb_way", - "mobile_pay", - "momo", - "momo_atm", - "multibanco", - "online_banking_thailand", - "online_banking_czech_republic", - "online_banking_finland", - "online_banking_fpx", - "online_banking_poland", - "online_banking_slovakia", - "oxxo", - "pago_efectivo", - "permata_bank_transfer", - "open_banking_uk", - "pay_bright", - "paypal", - "pix", - "pay_safe_card", - "przelewy24", - "prompt_pay", - "pse", - "red_compra", - "red_pagos", - "samsung_pay", - "sepa", - "sofort", - "swish", - "touch_n_go", - "trustly", - "twint", - "upi_collect", - "upi_intent", - "vipps", - "viet_qr", - "venmo", - "walley", - "we_chat_pay", - "seven_eleven", - "lawson", - "mini_stop", - "family_mart", - "seicomart", - "pay_easy", - "local_bank_transfer", - "mifinity", - "open_banking_pis" - ] - }, - "PaymentMethodUpdate": { - "type": "object", - "properties": { - "card": { - "allOf": [ - { - "$ref": "#/components/schemas/CardDetailUpdate" + { + "type": "object", + "required": [ + "mandate_payment" + ], + "properties": { + "mandate_payment": { + "type": "object" } + } + }, + { + "type": "object", + "required": [ + "reward" ], - "nullable": true + "properties": { + "reward": { + "type": "object" + } + } }, - "client_secret": { - "type": "string", - "description": "This is a 15 minute expiry token which shall be used from the client to authenticate and perform sessions from the SDK", - "example": "secret_k2uj3he2893eiu2d", - "nullable": true, - "maxLength": 30, - "minLength": 30 - } - }, - "additionalProperties": false - }, - "PaymentMethodsEnabled": { - "type": "object", - "description": "Details of all the payment methods enabled for the connector for the given merchant account", - "required": [ - "payment_method" - ], - "properties": { - "payment_method": { - "$ref": "#/components/schemas/PaymentMethod" + { + "type": "object", + "required": [ + "real_time_payment" + ], + "properties": { + "real_time_payment": { + "$ref": "#/components/schemas/RealTimePaymentDataResponse" + } + } }, - "payment_method_types": { - "type": "array", - "items": { - "$ref": "#/components/schemas/RequestPaymentMethodTypes" - }, - "description": "Subtype of payment method", - "example": [ - "credit" + { + "type": "object", + "required": [ + "upi" ], - "nullable": true - } - }, - "additionalProperties": false - }, - "PaymentProcessingDetails": { - "type": "object", - "required": [ - "payment_processing_certificate", - "payment_processing_certificate_key" - ], - "properties": { - "payment_processing_certificate": { - "type": "string" + "properties": { + "upi": { + "$ref": "#/components/schemas/UpiResponse" + } + } }, - "payment_processing_certificate_key": { - "type": "string" - } - } - }, - "PaymentProcessingDetailsAt": { - "oneOf": [ { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentProcessingDetails" - }, - { - "type": "object", - "required": [ - "payment_processing_details_at" - ], - "properties": { - "payment_processing_details_at": { - "type": "string", - "enum": [ - "Hyperswitch" - ] - } - } + "type": "object", + "required": [ + "voucher" + ], + "properties": { + "voucher": { + "$ref": "#/components/schemas/VoucherResponse" } - ] + } }, { "type": "object", "required": [ - "payment_processing_details_at" + "gift_card" ], "properties": { - "payment_processing_details_at": { - "type": "string", - "enum": [ - "Connector" - ] + "gift_card": { + "$ref": "#/components/schemas/GiftCardResponse" } } - } - ], - "discriminator": { - "propertyName": "payment_processing_details_at" - } - }, - "PaymentRetrieveBody": { - "type": "object", - "properties": { - "merchant_id": { - "type": "string", - "description": "The identifier for the Merchant Account.", - "nullable": true }, - "force_sync": { - "type": "boolean", - "description": "Decider to enable or disable the connector call for retrieve request", - "nullable": true + { + "type": "object", + "required": [ + "card_redirect" + ], + "properties": { + "card_redirect": { + "$ref": "#/components/schemas/CardRedirectResponse" + } + } }, - "client_secret": { - "type": "string", - "description": "This is a token which expires after 15 minutes, used from the client to authenticate and create sessions from the SDK", - "nullable": true + { + "type": "object", + "required": [ + "card_token" + ], + "properties": { + "card_token": { + "$ref": "#/components/schemas/CardTokenResponse" + } + } }, - "expand_captures": { - "type": "boolean", - "description": "If enabled provides list of captures linked to latest attempt", - "nullable": true + { + "type": "object", + "required": [ + "open_banking" + ], + "properties": { + "open_banking": { + "$ref": "#/components/schemas/OpenBankingResponse" + } + } }, - "expand_attempts": { - "type": "boolean", - "description": "If enabled provides list of attempts linked to payment intent", - "nullable": true + { + "type": "object", + "required": [ + "mobile_payment" + ], + "properties": { + "mobile_payment": { + "$ref": "#/components/schemas/MobilePaymentResponse" + } + } } - } - }, - "PaymentType": { - "type": "string", - "description": "The type of the payment that differentiates between normal and various types of mandate payments. Use 'setup_mandate' in case of zero auth flow.", - "enum": [ - "normal", - "new_mandate", - "setup_mandate", - "recurring_mandate" ] }, - "PaymentsCancelRequest": { - "type": "object", - "properties": { - "cancellation_reason": { - "type": "string", - "description": "The reason for the payment cancel", - "nullable": true - }, - "merchant_connector_details": { + "PaymentMethodDataResponseWithBilling": { + "allOf": [ + { "allOf": [ { - "$ref": "#/components/schemas/MerchantConnectorDetailsWrap" + "$ref": "#/components/schemas/PaymentMethodDataResponse" } ], "nullable": true + }, + { + "type": "object", + "properties": { + "billing": { + "allOf": [ + { + "$ref": "#/components/schemas/Address" + } + ], + "nullable": true + } + } + } + ] + }, + "PaymentMethodDeleteResponse": { + "type": "object", + "required": [ + "payment_method_id" + ], + "properties": { + "payment_method_id": { + "type": "string", + "description": "The unique identifier of the Payment method", + "example": "card_rGK4Vi5iSW70MY7J2mIg" } } }, - "PaymentsCaptureRequest": { + "PaymentMethodIntentConfirm": { "type": "object", "required": [ - "amount_to_capture" + "payment_method_data", + "payment_method_type", + "payment_method_subtype" ], "properties": { - "merchant_id": { + "customer_id": { "type": "string", - "description": "The unique identifier for the merchant", - "nullable": true - }, - "amount_to_capture": { - "type": "integer", - "format": "int64", - "description": "The Amount to be captured/ debited from the user's payment method.", - "example": 6540 - }, - "refund_uncaptured_amount": { - "type": "boolean", - "description": "Decider to refund the uncaptured amount", - "nullable": true + "description": "The unique identifier of the customer.", + "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", + "nullable": true, + "maxLength": 64, + "minLength": 1 }, - "statement_descriptor_suffix": { - "type": "string", - "description": "Provides information about a card payment that customers see on their statements.", - "nullable": true + "payment_method_data": { + "$ref": "#/components/schemas/PaymentMethodCreateData" }, - "statement_descriptor_prefix": { - "type": "string", - "description": "Concatenated with the statement descriptor suffix that’s set on the account to form the complete statement descriptor.", - "nullable": true + "payment_method_type": { + "$ref": "#/components/schemas/PaymentMethod" }, - "merchant_connector_details": { - "allOf": [ - { - "$ref": "#/components/schemas/MerchantConnectorDetailsWrap" - } - ], - "nullable": true + "payment_method_subtype": { + "$ref": "#/components/schemas/PaymentMethodType" } - } + }, + "additionalProperties": false }, - "PaymentsCompleteAuthorizeRequest": { + "PaymentMethodIntentCreate": { "type": "object", "required": [ - "client_secret" + "customer_id" ], "properties": { - "shipping": { + "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. Metadata is useful for storing additional, structured information on an object.", + "nullable": true + }, + "billing": { "allOf": [ { "$ref": "#/components/schemas/Address" @@ -12756,790 +13953,822 @@ ], "nullable": true }, - "client_secret": { + "customer_id": { "type": "string", - "description": "Client Secret" + "description": "The unique identifier of the customer.", + "example": "12345_cus_01926c58bc6e77c09e809964e72af8c8", + "maxLength": 64, + "minLength": 32 } - } + }, + "additionalProperties": false }, - "PaymentsConfirmRequest": { - "type": "object", - "properties": { - "amount": { - "type": "integer", - "format": "int64", - "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies)", - "example": 6540, - "nullable": true, - "minimum": 0 - }, - "currency": { - "allOf": [ - { - "$ref": "#/components/schemas/Currency" - } + "PaymentMethodIssuerCode": { + "type": "string", + "enum": [ + "jp_hdfc", + "jp_icici", + "jp_googlepay", + "jp_applepay", + "jp_phonepay", + "jp_wechat", + "jp_sofort", + "jp_giropay", + "jp_sepa", + "jp_bacs" + ] + }, + "PaymentMethodListData": { + "oneOf": [ + { + "type": "object", + "required": [ + "card" ], - "nullable": true - }, - "amount_to_capture": { - "type": "integer", - "format": "int64", - "description": "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. Also, it must be less than or equal to the original payment account.", - "example": 6540, - "nullable": true - }, - "shipping_cost": { - "type": "integer", - "format": "int64", - "description": "The shipping cost for the payment. This is required for tax calculation in some regions.", - "example": 6540, - "nullable": true + "properties": { + "card": { + "$ref": "#/components/schemas/CardDetailFromLocker" + } + } }, - "payment_id": { + { + "type": "object", + "required": [ + "bank" + ], + "properties": { + "bank": { + "$ref": "#/components/schemas/Bank" + } + } + } + ] + }, + "PaymentMethodListRequest": { + "type": "object", + "properties": { + "client_secret": { "type": "string", - "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant. The value for this field can be specified in the request, it will be auto generated otherwise and returned in the API response.", - "example": "pay_mbabizu24mvu3mela5njyhpit4", + "description": "This is a 15 minute expiry token which shall be used from the client to authenticate and perform sessions from the SDK", + "example": "secret_k2uj3he2893eiu2d", "nullable": true, "maxLength": 30, "minLength": 30 }, - "routing": { - "allOf": [ - { - "$ref": "#/components/schemas/StraightThroughAlgorithm" - } - ], - "nullable": true - }, - "connector": { + "accepted_countries": { "type": "array", "items": { - "$ref": "#/components/schemas/Connector" + "$ref": "#/components/schemas/CountryAlpha2" }, - "description": "This allows to manually select a connector with which the payment can go through.", + "description": "The two-letter ISO currency code", "example": [ - "stripe", - "adyen" - ], - "nullable": true - }, - "capture_method": { - "allOf": [ - { - "$ref": "#/components/schemas/CaptureMethod" - } - ], - "nullable": true - }, - "authentication_type": { - "allOf": [ - { - "$ref": "#/components/schemas/AuthenticationType" - } + "US", + "UK", + "IN" ], - "default": "three_ds", "nullable": true }, - "billing": { + "amount": { "allOf": [ { - "$ref": "#/components/schemas/Address" + "$ref": "#/components/schemas/MinorUnit" } ], "nullable": true }, - "confirm": { - "type": "boolean", - "description": "Whether to confirm the payment (if applicable). It can be used to completely process a payment by attaching a payment method, setting `confirm=true` and `capture_method = automatic` in the *Payments/Create API* request itself.", - "default": false, - "example": true, - "nullable": true - }, - "customer": { - "allOf": [ - { - "$ref": "#/components/schemas/CustomerDetails" - } + "accepted_currencies": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Currency" + }, + "description": "The three-letter ISO currency code", + "example": [ + "USD", + "EUR" ], "nullable": true }, - "customer_id": { - "type": "string", - "description": "The identifier for the customer", - "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", - "nullable": true, - "maxLength": 64, - "minLength": 1 - }, - "off_session": { + "recurring_enabled": { "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", + "description": "Indicates whether the payment method is eligible for recurring payments", "example": true, "nullable": true }, - "description": { - "type": "string", - "description": "A description for the payment", - "example": "It's my first payment request", - "nullable": true - }, - "return_url": { - "type": "string", - "description": "The URL to which you want the user to be redirected after the completion of the payment operation", - "example": "https://hyperswitch.io", - "nullable": true - }, - "setup_future_usage": { - "allOf": [ - { - "$ref": "#/components/schemas/FutureUsage" - } - ], - "nullable": true - }, - "payment_method_data": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodDataRequest" - } + "card_networks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CardNetwork" + }, + "description": "Indicates whether the payment method is eligible for card netwotks", + "example": [ + "visa", + "mastercard" ], "nullable": true }, - "payment_method": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethod" - } - ], + "limit": { + "type": "integer", + "format": "int64", + "description": "Indicates the limit of last used payment methods", + "example": 1, "nullable": true - }, - "payment_token": { + } + }, + "additionalProperties": false + }, + "PaymentMethodListResponse": { + "type": "object", + "required": [ + "currency", + "payment_methods", + "mandate_payment", + "show_surcharge_breakup_screen", + "request_external_three_ds_authentication", + "is_tax_calculation_enabled" + ], + "properties": { + "redirect_url": { "type": "string", - "description": "As Hyperswitch tokenises the sensitive details about the payments method, it provides the payment_token as a reference to a stored payment method, ensuring that the sensitive details are not exposed in any manner.", - "example": "187282ab-40ef-47a9-9206-5099ba31e432", - "nullable": true - }, - "shipping": { - "allOf": [ - { - "$ref": "#/components/schemas/Address" - } - ], + "description": "Redirect URL of the merchant", + "example": "https://www.google.com", "nullable": true }, - "statement_descriptor_name": { - "type": "string", - "description": "For non-card charges, you can use this value as the complete description that appears on your customers’ statements. Must contain at least one letter, maximum 22 characters.", - "example": "Hyperswitch Router", - "nullable": true, - "maxLength": 255 - }, - "statement_descriptor_suffix": { - "type": "string", - "description": "Provides information about a card payment that customers see on their statements. Concatenated with the prefix (shortened descriptor) or statement descriptor that’s set on the account to form the complete statement descriptor. Maximum 22 characters for the concatenated descriptor.", - "example": "Payment for shoes purchase", - "nullable": true, - "maxLength": 255 + "currency": { + "$ref": "#/components/schemas/Currency" }, - "order_details": { + "payment_methods": { "type": "array", "items": { - "$ref": "#/components/schemas/OrderDetailsWithAmount" + "$ref": "#/components/schemas/ResponsePaymentMethodsEnabled" }, - "description": "Use this object to capture the details about the different products for which the payment is being made. The sum of amount across different products here should be equal to the overall payment amount", - "example": "[{\n \"product_name\": \"Apple iPhone 16\",\n \"quantity\": 1,\n \"amount\" : 69000\n \"product_img_link\" : \"https://dummy-img-link.com\"\n }]", - "nullable": true - }, - "client_secret": { - "type": "string", - "description": "It's a token used for client side verification.", - "example": "pay_U42c409qyHwOkWo3vK60_secret_el9ksDkiB8hi6j9N78yo", - "nullable": true + "description": "Information about the payment method" }, - "mandate_data": { - "allOf": [ - { - "$ref": "#/components/schemas/MandateData" - } - ], - "nullable": true + "mandate_payment": { + "$ref": "#/components/schemas/MandateType" }, - "customer_acceptance": { - "allOf": [ - { - "$ref": "#/components/schemas/CustomerAcceptance" - } - ], + "merchant_name": { + "type": "string", "nullable": true }, - "mandate_id": { - "type": "string", - "description": "A unique identifier to link the payment to a mandate. To do Recurring payments after a mandate has been created, pass the mandate_id instead of payment_method_data", - "example": "mandate_iwer89rnjef349dni3", - "nullable": true, - "maxLength": 255 + "show_surcharge_breakup_screen": { + "type": "boolean", + "description": "flag to indicate if surcharge and tax breakup screen should be shown or not" }, - "browser_info": { + "payment_type": { "allOf": [ { - "$ref": "#/components/schemas/BrowserInformation" + "$ref": "#/components/schemas/PaymentType" } ], "nullable": true }, - "payment_experience": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentExperience" - } - ], - "nullable": true + "request_external_three_ds_authentication": { + "type": "boolean", + "description": "flag to indicate whether to perform external 3ds authentication", + "example": true }, - "payment_method_type": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodType" - } - ], + "collect_shipping_details_from_wallets": { + "type": "boolean", + "description": "flag that indicates whether to collect shipping details from wallets or from the customer", "nullable": true }, - "merchant_connector_details": { - "allOf": [ - { - "$ref": "#/components/schemas/MerchantConnectorDetailsWrap" - } - ], + "collect_billing_details_from_wallets": { + "type": "boolean", + "description": "flag that indicates whether to collect billing details from wallets or from the customer", "nullable": true }, - "allowed_payment_method_types": { + "is_tax_calculation_enabled": { + "type": "boolean", + "description": "flag that indicates whether to calculate tax on the order amount" + } + } + }, + "PaymentMethodListResponseForPayments": { + "type": "object", + "required": [ + "payment_methods_enabled" + ], + "properties": { + "payment_methods_enabled": { "type": "array", "items": { - "$ref": "#/components/schemas/PaymentMethodType" + "$ref": "#/components/schemas/ResponsePaymentMethodTypes" }, - "description": "Use this parameter to restrict the Payment Method Types to show for a given PaymentIntent", - "nullable": true + "description": "The list of payment methods that are enabled for the business profile" }, - "retry_action": { - "allOf": [ - { - "$ref": "#/components/schemas/RetryAction" - } - ], + "customer_payment_methods": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomerPaymentMethod" + }, + "description": "The list of payment methods that are saved by the given customer\nThis field is only returned if the customer_id is provided in the request", "nullable": true + } + } + }, + "PaymentMethodResponse": { + "type": "object", + "required": [ + "merchant_id", + "customer_id", + "payment_method_id", + "payment_method_type", + "recurring_enabled" + ], + "properties": { + "merchant_id": { + "type": "string", + "description": "Unique identifier for a merchant", + "example": "merchant_1671528864" }, - "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. Metadata is useful for storing additional, structured information on an object.", - "nullable": true + "customer_id": { + "type": "string", + "description": "The unique identifier of the customer.", + "example": "12345_cus_01926c58bc6e77c09e809964e72af8c8", + "maxLength": 64, + "minLength": 32 }, - "connector_metadata": { + "payment_method_id": { + "type": "string", + "description": "The unique identifier of the Payment method", + "example": "card_rGK4Vi5iSW70MY7J2mIg" + }, + "payment_method_type": { + "$ref": "#/components/schemas/PaymentMethod" + }, + "payment_method_subtype": { "allOf": [ { - "$ref": "#/components/schemas/ConnectorMetadata" + "$ref": "#/components/schemas/PaymentMethodType" } ], "nullable": true }, - "payment_link": { + "recurring_enabled": { "type": "boolean", - "description": "Whether to generate the payment link for this payment or not (if applicable)", - "default": false, - "example": true, + "description": "Indicates whether the payment method is eligible for recurring payments", + "example": true + }, + "created": { + "type": "string", + "format": "date-time", + "description": "A timestamp (ISO 8601 code) that determines when the payment method was created", + "example": "2023-01-18T11:04:09.922Z", "nullable": true }, - "payment_link_config": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentCreatePaymentLinkConfig" - } - ], + "last_used_at": { + "type": "string", + "format": "date-time", + "example": "2024-02-24T11:04:09.922Z", "nullable": true }, - "payment_link_config_id": { + "ephemeral_key": { "type": "string", - "description": "Custom payment link config id set at business profile, send only if business_specific_configs is configured", + "description": "For Client based calls", "nullable": true }, - "payment_type": { + "payment_method_data": { "allOf": [ { - "$ref": "#/components/schemas/PaymentType" + "$ref": "#/components/schemas/PaymentMethodResponseData" } ], "nullable": true + } + } + }, + "PaymentMethodResponseData": { + "oneOf": [ + { + "type": "object", + "required": [ + "card" + ], + "properties": { + "card": { + "$ref": "#/components/schemas/CardDetailFromLocker" + } + } + } + ] + }, + "PaymentMethodStatus": { + "type": "string", + "description": "Payment Method Status", + "enum": [ + "active", + "inactive", + "processing", + "awaiting_data" + ] + }, + "PaymentMethodSubtypeSpecificData": { + "oneOf": [ + { + "type": "object", + "required": [ + "card_networks" + ], + "properties": { + "card_networks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CardNetworkTypes" + } + } + } }, - "request_incremental_authorization": { - "type": "boolean", - "description": "Request an incremental authorization, i.e., increase the authorized amount on a confirmed payment before you capture it.", - "nullable": true - }, - "session_expiry": { - "type": "integer", - "format": "int32", - "description": "Will be used to expire client secret after certain amount of time to be supplied in seconds\n(900) for 15 mins", - "example": 900, - "nullable": true, - "minimum": 0 - }, - "frm_metadata": { + { + "type": "object", + "required": [ + "bank_names" + ], + "properties": { + "bank_names": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BankCodeResponse" + } + } + } + } + ] + }, + "PaymentMethodType": { + "type": "string", + "description": "Indicates the sub type of payment method. Eg: 'google_pay' & 'apple_pay' for wallets.", + "enum": [ + "ach", + "affirm", + "afterpay_clearpay", + "alfamart", + "ali_pay", + "ali_pay_hk", + "alma", + "apple_pay", + "atome", + "bacs", + "bancontact_card", + "becs", + "benefit", + "bizum", + "blik", + "boleto", + "bca_bank_transfer", + "bni_va", + "bri_va", + "card_redirect", + "cimb_va", + "classic", + "credit", + "crypto_currency", + "cashapp", + "dana", + "danamon_va", + "debit", + "duit_now", + "efecty", + "eps", + "fps", + "evoucher", + "giropay", + "givex", + "google_pay", + "go_pay", + "gcash", + "ideal", + "interac", + "indomaret", + "klarna", + "kakao_pay", + "local_bank_redirect", + "mandiri_va", + "knet", + "mb_way", + "mobile_pay", + "momo", + "momo_atm", + "multibanco", + "online_banking_thailand", + "online_banking_czech_republic", + "online_banking_finland", + "online_banking_fpx", + "online_banking_poland", + "online_banking_slovakia", + "oxxo", + "pago_efectivo", + "permata_bank_transfer", + "open_banking_uk", + "pay_bright", + "paypal", + "paze", + "pix", + "pay_safe_card", + "przelewy24", + "prompt_pay", + "pse", + "red_compra", + "red_pagos", + "samsung_pay", + "sepa", + "sofort", + "swish", + "touch_n_go", + "trustly", + "twint", + "upi_collect", + "upi_intent", + "vipps", + "viet_qr", + "venmo", + "walley", + "we_chat_pay", + "seven_eleven", + "lawson", + "mini_stop", + "family_mart", + "seicomart", + "pay_easy", + "local_bank_transfer", + "mifinity", + "open_banking_pis", + "direct_carrier_billing" + ] + }, + "PaymentMethodUpdate": { + "type": "object", + "required": [ + "payment_method_data" + ], + "properties": { + "payment_method_data": { + "$ref": "#/components/schemas/PaymentMethodUpdateData" + } + }, + "additionalProperties": false + }, + "PaymentMethodUpdateData": { + "oneOf": [ + { "type": "object", - "description": "Additional data related to some frm(Fraud Risk Management) connectors", - "nullable": true - }, - "request_external_three_ds_authentication": { - "type": "boolean", - "description": "Whether to perform external authentication (if applicable)", - "example": true, - "nullable": true - }, - "recurring_details": { - "allOf": [ - { - "$ref": "#/components/schemas/RecurringDetails" - } + "required": [ + "card" ], - "nullable": true - }, - "charges": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentChargeRequest" + "properties": { + "card": { + "$ref": "#/components/schemas/CardDetailUpdate" } - ], - "nullable": true - }, - "merchant_order_reference_id": { - "type": "string", - "description": "Merchant's identifier for the payment/invoice. This will be sent to the connector\nif the connector provides support to accept multiple reference ids.\nIn case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference.", - "example": "Custom_Order_id_123", - "nullable": true, - "maxLength": 255 - }, - "skip_external_tax_calculation": { - "type": "boolean", - "description": "Whether to calculate tax for this payment intent", - "nullable": true + } } - } + ] }, - "PaymentsCreateIntentRequest": { + "PaymentMethodsEnabled": { "type": "object", + "description": "Details of all the payment methods enabled for the connector for the given merchant account", "required": [ - "amount_details" + "payment_method_type" ], "properties": { - "amount_details": { - "$ref": "#/components/schemas/AmountDetails" - }, - "merchant_reference_id": { - "type": "string", - "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant.", - "example": "pay_mbabizu24mvu3mela5njyhpit4", - "nullable": true, - "maxLength": 30, - "minLength": 30 - }, - "routing_algorithm_id": { - "type": "string", - "description": "The routing algorithm id to be used for the payment", - "nullable": true - }, - "capture_method": { - "allOf": [ - { - "$ref": "#/components/schemas/CaptureMethod" - } - ], - "nullable": true - }, - "authentication_type": { - "allOf": [ - { - "$ref": "#/components/schemas/AuthenticationType" - } - ], - "default": "no_three_ds", - "nullable": true + "payment_method_type": { + "$ref": "#/components/schemas/PaymentMethod" }, - "billing": { - "allOf": [ - { - "$ref": "#/components/schemas/Address" - } - ], + "payment_method_subtypes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RequestPaymentMethodTypes" + }, + "description": "Payment method configuration, this includes all the filters associated with the payment method", "nullable": true + } + }, + "additionalProperties": false + }, + "PaymentProcessingDetails": { + "type": "object", + "required": [ + "payment_processing_certificate", + "payment_processing_certificate_key" + ], + "properties": { + "payment_processing_certificate": { + "type": "string" }, - "shipping": { + "payment_processing_certificate_key": { + "type": "string" + } + } + }, + "PaymentProcessingDetailsAt": { + "oneOf": [ + { "allOf": [ { - "$ref": "#/components/schemas/Address" - } - ], - "nullable": true - }, - "customer_id": { - "type": "string", - "description": "The identifier for the customer", - "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", - "nullable": true, - "maxLength": 64, - "minLength": 1 - }, - "customer_present": { - "allOf": [ + "$ref": "#/components/schemas/PaymentProcessingDetails" + }, { - "$ref": "#/components/schemas/PresenceOfCustomerDuringPayment" + "type": "object", + "required": [ + "payment_processing_details_at" + ], + "properties": { + "payment_processing_details_at": { + "type": "string", + "enum": [ + "Hyperswitch" + ] + } + } } - ], - "nullable": true - }, - "description": { - "type": "string", - "description": "A description for the payment", - "example": "It's my first payment request", - "nullable": true - }, - "return_url": { - "type": "string", - "description": "The URL to which you want the user to be redirected after the completion of the payment operation", - "example": "https://hyperswitch.io", - "nullable": true + ] }, - "setup_future_usage": { - "allOf": [ - { - "$ref": "#/components/schemas/FutureUsage" - } + { + "type": "object", + "required": [ + "payment_processing_details_at" ], - "nullable": true - }, - "apply_mit_exemption": { - "allOf": [ - { - "$ref": "#/components/schemas/MitExemptionRequest" + "properties": { + "payment_processing_details_at": { + "type": "string", + "enum": [ + "Connector" + ] } - ], - "nullable": true - }, - "statement_descriptor": { - "type": "string", - "description": "For non-card charges, you can use this value as the complete description that appears on your customers’ statements. Must contain at least one letter, maximum 22 characters.", - "example": "Hyperswitch Router", - "nullable": true, - "maxLength": 22 - }, - "order_details": { - "type": "array", - "items": { - "$ref": "#/components/schemas/OrderDetailsWithAmount" - }, - "description": "Use this object to capture the details about the different products for which the payment is being made. The sum of amount across different products here should be equal to the overall payment amount", - "example": "[{\n \"product_name\": \"Apple iPhone 16\",\n \"quantity\": 1,\n \"amount\" : 69000\n \"product_img_link\" : \"https://dummy-img-link.com\"\n }]", - "nullable": true - }, - "allowed_payment_method_types": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PaymentMethodType" - }, - "description": "Use this parameter to restrict the Payment Method Types to show for a given PaymentIntent", + } + } + ], + "discriminator": { + "propertyName": "payment_processing_details_at" + } + }, + "PaymentRetrieveBody": { + "type": "object", + "properties": { + "merchant_id": { + "type": "string", + "description": "The identifier for the Merchant Account.", "nullable": true }, - "metadata": { - "type": "object", - "description": "Metadata is useful for storing additional, unstructured information on an object.", + "force_sync": { + "type": "boolean", + "description": "Decider to enable or disable the connector call for retrieve request", "nullable": true }, - "connector_metadata": { - "allOf": [ - { - "$ref": "#/components/schemas/ConnectorMetadata" - } - ], + "client_secret": { + "type": "string", + "description": "This is a token which expires after 15 minutes, used from the client to authenticate and create sessions from the SDK", "nullable": true }, - "feature_metadata": { - "allOf": [ - { - "$ref": "#/components/schemas/FeatureMetadata" - } - ], + "expand_captures": { + "type": "boolean", + "description": "If enabled provides list of captures linked to latest attempt", "nullable": true }, - "payment_link_enabled": { - "allOf": [ - { - "$ref": "#/components/schemas/EnablePaymentLinkRequest" - } - ], + "expand_attempts": { + "type": "boolean", + "description": "If enabled provides list of attempts linked to payment intent", "nullable": true - }, - "payment_link_config": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentCreatePaymentLinkConfig" - } - ], + } + } + }, + "PaymentType": { + "type": "string", + "description": "The type of the payment that differentiates between normal and various types of mandate payments. Use 'setup_mandate' in case of zero auth flow.", + "enum": [ + "normal", + "new_mandate", + "setup_mandate", + "recurring_mandate" + ] + }, + "PaymentsCancelRequest": { + "type": "object", + "properties": { + "cancellation_reason": { + "type": "string", + "description": "The reason for the payment cancel", "nullable": true }, - "request_incremental_authorization": { + "merchant_connector_details": { "allOf": [ { - "$ref": "#/components/schemas/RequestIncrementalAuthorization" + "$ref": "#/components/schemas/MerchantConnectorDetailsWrap" } ], "nullable": true - }, - "session_expiry": { + } + } + }, + "PaymentsCaptureRequest": { + "type": "object", + "properties": { + "amount_to_capture": { "type": "integer", - "format": "int32", - "description": "Will be used to expire client secret after certain amount of time to be supplied in seconds, if not sent it will be taken from profile config\n(900) for 15 mins", - "example": 900, - "nullable": true, - "minimum": 0 - }, - "frm_metadata": { - "type": "object", - "description": "Additional data related to some frm(Fraud Risk Management) connectors", + "format": "int64", + "description": "The Amount to be captured/ debited from the user's payment method. If not passed the full amount will be captured.", + "example": 6540, "nullable": true - }, - "request_external_three_ds_authentication": { + } + } + }, + "PaymentsCompleteAuthorizeRequest": { + "type": "object", + "required": [ + "client_secret" + ], + "properties": { + "shipping": { "allOf": [ { - "$ref": "#/components/schemas/External3dsAuthenticationRequest" + "$ref": "#/components/schemas/Address" } ], "nullable": true + }, + "client_secret": { + "type": "string", + "description": "Client Secret" } - }, - "additionalProperties": false + } }, - "PaymentsCreateIntentResponse": { + "PaymentsConfirmIntentRequest": { "type": "object", + "description": "Request for Payment Intent Confirm", "required": [ - "amount_details", - "client_secret", - "capture_method", - "authentication_type", - "customer_present", - "setup_future_usage", - "apply_mit_exemption", - "payment_link_enabled", - "request_incremental_authorization", - "request_external_three_ds_authentication" + "payment_method_data", + "payment_method_type", + "payment_method_subtype" ], "properties": { - "amount_details": { - "$ref": "#/components/schemas/AmountDetails" - }, - "client_secret": { + "return_url": { "type": "string", - "description": "It's a token used for client side verification.", - "example": "pay_U42c409qyHwOkWo3vK60_secret_el9ksDkiB8hi6j9N78yo" + "description": "The URL to which you want the user to be redirected after the completion of the payment operation\nIf this url is not passed, the url configured in the business profile will be used", + "example": "https://hyperswitch.io", + "nullable": true }, - "merchant_reference_id": { - "type": "string", - "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant.", - "example": "pay_mbabizu24mvu3mela5njyhpit4", - "nullable": true, - "maxLength": 30, - "minLength": 30 + "payment_method_data": { + "$ref": "#/components/schemas/PaymentMethodDataRequest" }, - "routing_algorithm_id": { - "type": "string", - "description": "The routing algorithm id to be used for the payment", - "nullable": true + "payment_method_type": { + "$ref": "#/components/schemas/PaymentMethod" }, - "capture_method": { - "$ref": "#/components/schemas/CaptureMethod" + "payment_method_subtype": { + "$ref": "#/components/schemas/PaymentMethodType" }, - "authentication_type": { + "shipping": { "allOf": [ { - "$ref": "#/components/schemas/AuthenticationType" + "$ref": "#/components/schemas/Address" } ], - "default": "no_three_ds" + "nullable": true }, - "billing": { + "customer_acceptance": { "allOf": [ { - "$ref": "#/components/schemas/Address" + "$ref": "#/components/schemas/CustomerAcceptance" } ], "nullable": true }, - "shipping": { + "browser_info": { "allOf": [ { - "$ref": "#/components/schemas/Address" + "$ref": "#/components/schemas/BrowserInformation" } ], "nullable": true + } + }, + "additionalProperties": false + }, + "PaymentsConfirmIntentResponse": { + "type": "object", + "description": "Response for Payment Intent Confirm", + "required": [ + "id", + "status", + "amount", + "customer_id", + "connector", + "client_secret", + "created", + "payment_method_type", + "payment_method_subtype", + "merchant_connector_id" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant.", + "example": "12345_pay_01926c58bc6e77c09e809964e72af8c8", + "maxLength": 64, + "minLength": 32 + }, + "status": { + "$ref": "#/components/schemas/IntentStatus" + }, + "amount": { + "$ref": "#/components/schemas/PaymentAmountDetailsResponse" }, "customer_id": { "type": "string", "description": "The identifier for the customer", - "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", - "nullable": true, + "example": "12345_cus_01926c58bc6e77c09e809964e72af8c8", "maxLength": 64, - "minLength": 1 + "minLength": 32 }, - "customer_present": { - "$ref": "#/components/schemas/PresenceOfCustomerDuringPayment" - }, - "description": { + "connector": { "type": "string", - "description": "A description for the payment", - "example": "It's my first payment request", - "nullable": true + "description": "The connector used for the payment", + "example": "stripe" }, - "return_url": { + "client_secret": { "type": "string", - "description": "The URL to which you want the user to be redirected after the completion of the payment operation", - "example": "https://hyperswitch.io", - "nullable": true - }, - "setup_future_usage": { - "$ref": "#/components/schemas/FutureUsage" - }, - "apply_mit_exemption": { - "$ref": "#/components/schemas/MitExemptionRequest" + "description": "It's a token used for client side verification." }, - "statement_descriptor": { + "created": { "type": "string", - "description": "For non-card charges, you can use this value as the complete description that appears on your customers’ statements. Must contain at least one letter, maximum 22 characters.", - "example": "Hyperswitch Router", - "nullable": true, - "maxLength": 22 + "format": "date-time", + "description": "Time when the payment was created", + "example": "2022-09-10T10:11:12Z" }, - "order_details": { - "type": "array", - "items": { - "$ref": "#/components/schemas/OrderDetailsWithAmount" - }, - "description": "Use this object to capture the details about the different products for which the payment is being made. The sum of amount across different products here should be equal to the overall payment amount", - "example": "[{\n \"product_name\": \"Apple iPhone 16\",\n \"quantity\": 1,\n \"amount\" : 69000\n \"product_img_link\" : \"https://dummy-img-link.com\"\n }]", + "payment_method_data": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentMethodDataResponseWithBilling" + } + ], "nullable": true }, - "allowed_payment_method_types": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PaymentMethodType" - }, - "description": "Use this parameter to restrict the Payment Method Types to show for a given PaymentIntent", - "nullable": true + "payment_method_type": { + "$ref": "#/components/schemas/PaymentMethod" }, - "metadata": { - "type": "object", - "description": "Metadata is useful for storing additional, unstructured information on an object.", - "nullable": true + "payment_method_subtype": { + "$ref": "#/components/schemas/PaymentMethodType" }, - "connector_metadata": { + "next_action": { "allOf": [ { - "$ref": "#/components/schemas/ConnectorMetadata" + "$ref": "#/components/schemas/NextActionData" } ], "nullable": true }, - "feature_metadata": { + "connector_transaction_id": { + "type": "string", + "description": "A unique identifier for a payment provided by the connector", + "example": "993672945374576J", + "nullable": true + }, + "connector_reference_id": { + "type": "string", + "description": "reference(Identifier) to the payment at connector side", + "example": "993672945374576J", + "nullable": true + }, + "merchant_connector_id": { + "type": "string", + "description": "Identifier of the connector ( merchant connector account ) which was chosen to make the payment" + }, + "browser_info": { "allOf": [ { - "$ref": "#/components/schemas/FeatureMetadata" + "$ref": "#/components/schemas/BrowserInformation" } ], "nullable": true }, - "payment_link_enabled": { - "$ref": "#/components/schemas/EnablePaymentLinkRequest" - }, - "payment_link_config": { + "error": { "allOf": [ { - "$ref": "#/components/schemas/PaymentCreatePaymentLinkConfig" + "$ref": "#/components/schemas/ErrorDetails" } ], "nullable": true - }, - "request_incremental_authorization": { - "$ref": "#/components/schemas/RequestIncrementalAuthorization" - }, - "session_expiry": { - "type": "integer", - "format": "int32", - "description": "Will be used to expire client secret after certain amount of time to be supplied in seconds, if not sent it will be taken from profile config\n(900) for 15 mins", - "example": 900, - "nullable": true, - "minimum": 0 - }, - "frm_metadata": { - "type": "object", - "description": "Additional data related to some frm(Fraud Risk Management) connectors", - "nullable": true - }, - "request_external_three_ds_authentication": { - "$ref": "#/components/schemas/External3dsAuthenticationRequest" } - }, - "additionalProperties": false + } }, - "PaymentsCreateRequest": { + "PaymentsCreateIntentRequest": { "type": "object", "required": [ - "amount", - "currency" + "amount_details", + "customer_id" ], "properties": { - "amount": { - "type": "integer", - "format": "int64", - "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies)", - "minimum": 0 - }, - "currency": { - "$ref": "#/components/schemas/Currency" - }, - "amount_to_capture": { - "type": "integer", - "format": "int64", - "description": "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. Also, it must be less than or equal to the original payment account.", - "example": 6540, - "nullable": true - }, - "shipping_cost": { - "type": "integer", - "format": "int64", - "description": "The shipping cost for the payment. This is required for tax calculation in some regions.", - "example": 6540, - "nullable": true + "amount_details": { + "$ref": "#/components/schemas/AmountDetails" }, - "payment_id": { + "merchant_reference_id": { "type": "string", - "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant. The value for this field can be specified in the request, it will be auto generated otherwise and returned in the API response.", + "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant.", "example": "pay_mbabizu24mvu3mela5njyhpit4", "nullable": true, "maxLength": 30, "minLength": 30 }, - "routing": { - "allOf": [ - { - "$ref": "#/components/schemas/StraightThroughAlgorithm" - } - ], - "nullable": true - }, - "connector": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Connector" - }, - "description": "This allows to manually select a connector with which the payment can go through.", - "example": [ - "stripe", - "adyen" - ], + "routing_algorithm_id": { + "type": "string", + "description": "The routing algorithm id to be used for the payment", "nullable": true }, "capture_method": { @@ -13556,7 +14785,7 @@ "$ref": "#/components/schemas/AuthenticationType" } ], - "default": "three_ds", + "default": "no_three_ds", "nullable": true }, "billing": { @@ -13567,17 +14796,10 @@ ], "nullable": true }, - "confirm": { - "type": "boolean", - "description": "Whether to confirm the payment (if applicable). It can be used to completely process a payment by attaching a payment method, setting `confirm=true` and `capture_method = automatic` in the *Payments/Create API* request itself.", - "default": false, - "example": true, - "nullable": true - }, - "customer": { + "shipping": { "allOf": [ { - "$ref": "#/components/schemas/CustomerDetails" + "$ref": "#/components/schemas/Address" } ], "nullable": true @@ -13585,15 +14807,16 @@ "customer_id": { "type": "string", "description": "The identifier for the customer", - "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", - "nullable": true, + "example": "12345_cus_01926c58bc6e77c09e809964e72af8c8", "maxLength": 64, - "minLength": 1 + "minLength": 32 }, - "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", - "example": true, + "customer_present": { + "allOf": [ + { + "$ref": "#/components/schemas/PresenceOfCustomerDuringPayment" + } + ], "nullable": true }, "description": { @@ -13616,49 +14839,20 @@ ], "nullable": true }, - "payment_method_data": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodDataRequest" - } - ], - "nullable": true - }, - "payment_method": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethod" - } - ], - "nullable": true - }, - "payment_token": { - "type": "string", - "description": "As Hyperswitch tokenises the sensitive details about the payments method, it provides the payment_token as a reference to a stored payment method, ensuring that the sensitive details are not exposed in any manner.", - "example": "187282ab-40ef-47a9-9206-5099ba31e432", - "nullable": true - }, - "shipping": { + "apply_mit_exemption": { "allOf": [ { - "$ref": "#/components/schemas/Address" + "$ref": "#/components/schemas/MitExemptionRequest" } ], "nullable": true }, - "statement_descriptor_name": { + "statement_descriptor": { "type": "string", "description": "For non-card charges, you can use this value as the complete description that appears on your customers’ statements. Must contain at least one letter, maximum 22 characters.", "example": "Hyperswitch Router", "nullable": true, - "maxLength": 255 - }, - "statement_descriptor_suffix": { - "type": "string", - "description": "Provides information about a card payment that customers see on their statements. Concatenated with the prefix (shortened descriptor) or statement descriptor that’s set on the account to form the complete statement descriptor. Maximum 22 characters for the concatenated descriptor.", - "example": "Payment for shoes purchase", - "nullable": true, - "maxLength": 255 + "maxLength": 22 }, "order_details": { "type": "array", @@ -13669,75 +14863,6 @@ "example": "[{\n \"product_name\": \"Apple iPhone 16\",\n \"quantity\": 1,\n \"amount\" : 69000\n \"product_img_link\" : \"https://dummy-img-link.com\"\n }]", "nullable": true }, - "mandate_data": { - "allOf": [ - { - "$ref": "#/components/schemas/MandateData" - } - ], - "nullable": true - }, - "customer_acceptance": { - "allOf": [ - { - "$ref": "#/components/schemas/CustomerAcceptance" - } - ], - "nullable": true - }, - "mandate_id": { - "type": "string", - "description": "A unique identifier to link the payment to a mandate. To do Recurring payments after a mandate has been created, pass the mandate_id instead of payment_method_data", - "example": "mandate_iwer89rnjef349dni3", - "nullable": true, - "maxLength": 255 - }, - "browser_info": { - "allOf": [ - { - "$ref": "#/components/schemas/BrowserInformation" - } - ], - "nullable": true - }, - "payment_experience": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentExperience" - } - ], - "nullable": true - }, - "payment_method_type": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodType" - } - ], - "nullable": true - }, - "business_country": { - "allOf": [ - { - "$ref": "#/components/schemas/CountryAlpha2" - } - ], - "nullable": true - }, - "business_label": { - "type": "string", - "description": "Business label of the merchant for this payment.\nTo be deprecated soon. Pass the profile_id instead", - "example": "food", - "nullable": true - }, - "merchant_connector_details": { - "allOf": [ - { - "$ref": "#/components/schemas/MerchantConnectorDetailsWrap" - } - ], - "nullable": true - }, "allowed_payment_method_types": { "type": "array", "items": { @@ -13748,7 +14873,7 @@ }, "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. Metadata is useful for storing additional, structured information on an object.", + "description": "Metadata is useful for storing additional, unstructured information on an object.", "nullable": true }, "connector_metadata": { @@ -13759,100 +14884,61 @@ ], "nullable": true }, - "payment_link": { - "type": "boolean", - "description": "Whether to generate the payment link for this payment or not (if applicable)", - "default": false, - "example": true, - "nullable": true - }, - "payment_link_config": { + "feature_metadata": { "allOf": [ { - "$ref": "#/components/schemas/PaymentCreatePaymentLinkConfig" + "$ref": "#/components/schemas/FeatureMetadata" } ], "nullable": true }, - "payment_link_config_id": { - "type": "string", - "description": "Custom payment link config id set at business profile, send only if business_specific_configs is configured", - "nullable": true - }, - "profile_id": { - "type": "string", - "description": "The business profile to be used for this payment, if not passed the default business profile associated with the merchant account will be used. It is mandatory in case multiple business profiles have been set up.", - "nullable": true - }, - "surcharge_details": { + "payment_link_enabled": { "allOf": [ { - "$ref": "#/components/schemas/RequestSurchargeDetails" + "$ref": "#/components/schemas/EnablePaymentLinkRequest" } ], "nullable": true }, - "payment_type": { + "payment_link_config": { "allOf": [ { - "$ref": "#/components/schemas/PaymentType" + "$ref": "#/components/schemas/PaymentLinkConfigRequest" } ], "nullable": true }, "request_incremental_authorization": { - "type": "boolean", - "description": "Request an incremental authorization, i.e., increase the authorized amount on a confirmed payment before you capture it.", + "allOf": [ + { + "$ref": "#/components/schemas/RequestIncrementalAuthorization" + } + ], "nullable": true }, "session_expiry": { "type": "integer", "format": "int32", - "description": "Will be used to expire client secret after certain amount of time to be supplied in seconds\n(900) for 15 mins", + "description": "Will be used to expire client secret after certain amount of time to be supplied in seconds, if not sent it will be taken from profile config\n(900) for 15 mins", "example": 900, "nullable": true, - "minimum": 0 - }, - "frm_metadata": { - "type": "object", - "description": "Additional data related to some frm(Fraud Risk Management) connectors", - "nullable": true - }, - "request_external_three_ds_authentication": { - "type": "boolean", - "description": "Whether to perform external authentication (if applicable)", - "example": true, - "nullable": true + "minimum": 0 }, - "recurring_details": { - "allOf": [ - { - "$ref": "#/components/schemas/RecurringDetails" - } - ], + "frm_metadata": { + "type": "object", + "description": "Additional data related to some frm(Fraud Risk Management) connectors", "nullable": true }, - "charges": { + "request_external_three_ds_authentication": { "allOf": [ { - "$ref": "#/components/schemas/PaymentChargeRequest" + "$ref": "#/components/schemas/External3dsAuthenticationRequest" } ], "nullable": true - }, - "merchant_order_reference_id": { - "type": "string", - "description": "Merchant's identifier for the payment/invoice. This will be sent to the connector\nif the connector provides support to accept multiple reference ids.\nIn case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference.", - "example": "Custom_Order_id_123", - "nullable": true, - "maxLength": 255 - }, - "skip_external_tax_calculation": { - "type": "boolean", - "description": "Whether to calculate tax for this payment intent", - "nullable": true } - } + }, + "additionalProperties": false }, "PaymentsCreateResponseOpenApi": { "type": "object", @@ -13901,6 +14987,13 @@ "description": "The payment net amount. net_amount = amount + surcharge_details.surcharge_amount + surcharge_details.tax_amount + shipping_cost + order_tax_amount,\nIf no surcharge_details, shipping_cost, order_tax_amount, net_amount = amount", "example": 6540 }, + "shipping_cost": { + "type": "integer", + "format": "int64", + "description": "The shipping cost for the payment.", + "example": 6540, + "nullable": true + }, "amount_capturable": { "type": "integer", "format": "int64", @@ -13944,910 +15037,723 @@ "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", "nullable": true, "maxLength": 64, - "minLength": 1 - }, - "description": { - "type": "string", - "description": "A description of the payment", - "example": "It's my first payment request", - "nullable": true - }, - "refunds": { - "type": "array", - "items": { - "$ref": "#/components/schemas/RefundResponse" - }, - "description": "List of refunds that happened on this intent, as same payment intent can have multiple refund requests depending on the nature of order", - "nullable": true - }, - "disputes": { - "type": "array", - "items": { - "$ref": "#/components/schemas/DisputeResponsePaymentsRetrieve" - }, - "description": "List of disputes that happened on this intent", - "nullable": true - }, - "attempts": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PaymentAttemptResponse" - }, - "description": "List of attempts that happened on this intent", - "nullable": true - }, - "captures": { - "type": "array", - "items": { - "$ref": "#/components/schemas/CaptureResponse" - }, - "description": "List of captures done on latest attempt", - "nullable": true - }, - "mandate_id": { - "type": "string", - "description": "A unique identifier to link the payment to a mandate, can be used instead of payment_method_data, in case of setting up recurring payments", - "example": "mandate_iwer89rnjef349dni3", - "nullable": true, - "maxLength": 255 - }, - "mandate_data": { - "allOf": [ - { - "$ref": "#/components/schemas/MandateData" - } - ], - "nullable": true - }, - "setup_future_usage": { - "allOf": [ - { - "$ref": "#/components/schemas/FutureUsage" - } - ], - "nullable": true - }, - "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. This parameter can only be used with confirm=true.", - "example": true, - "nullable": true - }, - "capture_method": { - "allOf": [ - { - "$ref": "#/components/schemas/CaptureMethod" - } - ], - "nullable": true - }, - "payment_method": { - "$ref": "#/components/schemas/PaymentMethod" - }, - "payment_method_data": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodDataResponseWithBilling" - } - ], - "nullable": true - }, - "payment_token": { - "type": "string", - "description": "Provide a reference to a stored payment method", - "example": "187282ab-40ef-47a9-9206-5099ba31e432", - "nullable": true - }, - "shipping": { - "allOf": [ - { - "$ref": "#/components/schemas/Address" - } - ], - "nullable": true - }, - "billing": { - "allOf": [ - { - "$ref": "#/components/schemas/Address" - } - ], - "nullable": true - }, - "order_details": { - "type": "array", - "items": { - "$ref": "#/components/schemas/OrderDetailsWithAmount" - }, - "description": "Information about the product , quantity and amount for connectors. (e.g. Klarna)", - "example": "[{\n \"product_name\": \"gillete creme\",\n \"quantity\": 15,\n \"amount\" : 900\n }]", - "nullable": true - }, - "email": { - "type": "string", - "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\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\nThis field will be deprecated soon. Please refer to `customer.phone` object", - "deprecated": true, - "example": "9123456789", - "nullable": true, - "maxLength": 255 - }, - "return_url": { - "type": "string", - "description": "The URL to redirect after the completion of the operation", - "example": "https://hyperswitch.io", - "nullable": true - }, - "authentication_type": { - "allOf": [ - { - "$ref": "#/components/schemas/AuthenticationType" - } - ], - "default": "three_ds", - "nullable": true - }, - "statement_descriptor_name": { - "type": "string", - "description": "For non-card charges, you can use this value as the complete description that appears on your customers’ statements. Must contain at least one letter, maximum 22 characters.", - "example": "Hyperswitch Router", - "nullable": true, - "maxLength": 255 - }, - "statement_descriptor_suffix": { - "type": "string", - "description": "Provides information about a card payment that customers see on their statements. Concatenated with the prefix (shortened descriptor) or statement descriptor that’s set on the account to form the complete statement descriptor. Maximum 255 characters for the concatenated descriptor.", - "example": "Payment for shoes purchase", - "nullable": true, - "maxLength": 255 - }, - "next_action": { - "allOf": [ - { - "$ref": "#/components/schemas/NextActionData" - } - ], - "nullable": true - }, - "cancellation_reason": { - "type": "string", - "description": "If the payment was cancelled the reason will be provided here", - "nullable": true - }, - "error_code": { - "type": "string", - "description": "If there was an error while calling the connectors the code is received here", - "example": "E0001", - "nullable": true - }, - "error_message": { - "type": "string", - "description": "If there was an error while calling the connector the error message is received here", - "example": "Failed while verifying the card", - "nullable": true - }, - "payment_experience": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentExperience" - } - ], - "nullable": true - }, - "payment_method_type": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodType" - } - ], - "nullable": true - }, - "connector_label": { - "type": "string", - "description": "The connector used for this payment along with the country and business details", - "example": "stripe_US_food", - "nullable": true - }, - "business_country": { - "allOf": [ - { - "$ref": "#/components/schemas/CountryAlpha2" - } - ], - "nullable": true + "minLength": 1 }, - "business_label": { + "description": { "type": "string", - "description": "The business label of merchant for this payment", + "description": "A description of the payment", + "example": "It's my first payment request", "nullable": true }, - "business_sub_label": { - "type": "string", - "description": "The business_sub_label for this payment", + "refunds": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RefundResponse" + }, + "description": "List of refunds that happened on this intent, as same payment intent can have multiple refund requests depending on the nature of order", "nullable": true }, - "allowed_payment_method_types": { + "disputes": { "type": "array", "items": { - "$ref": "#/components/schemas/PaymentMethodType" + "$ref": "#/components/schemas/DisputeResponsePaymentsRetrieve" }, - "description": "Allowed Payment Method Types for a given PaymentIntent", + "description": "List of disputes that happened on this intent", "nullable": true }, - "ephemeral_key": { - "allOf": [ - { - "$ref": "#/components/schemas/EphemeralKeyCreateResponse" - } - ], + "attempts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PaymentAttemptResponse" + }, + "description": "List of attempts that happened on this intent", "nullable": true }, - "manual_retry_allowed": { - "type": "boolean", - "description": "If true the payment can be retried with same or different payment method which means the confirm call can be made again.", + "captures": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaptureResponse" + }, + "description": "List of captures done on latest attempt", "nullable": true }, - "connector_transaction_id": { + "mandate_id": { "type": "string", - "description": "A unique identifier for a payment provided by the connector", - "example": "993672945374576J", - "nullable": true + "description": "A unique identifier to link the payment to a mandate, can be used instead of payment_method_data, in case of setting up recurring payments", + "example": "mandate_iwer89rnjef349dni3", + "nullable": true, + "maxLength": 255 }, - "frm_message": { + "mandate_data": { "allOf": [ { - "$ref": "#/components/schemas/FrmMessage" + "$ref": "#/components/schemas/MandateData" } ], "nullable": true }, - "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. Metadata is useful for storing additional, structured information on an object.", - "nullable": true - }, - "connector_metadata": { + "setup_future_usage": { "allOf": [ { - "$ref": "#/components/schemas/ConnectorMetadata" + "$ref": "#/components/schemas/FutureUsage" } ], "nullable": true }, - "feature_metadata": { + "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. This parameter can only be used with confirm=true.", + "example": true, + "nullable": true + }, + "capture_method": { "allOf": [ { - "$ref": "#/components/schemas/FeatureMetadata" + "$ref": "#/components/schemas/CaptureMethod" } ], "nullable": true }, - "reference_id": { - "type": "string", - "description": "reference(Identifier) to the payment at connector side", - "example": "993672945374576J", - "nullable": true + "payment_method": { + "$ref": "#/components/schemas/PaymentMethod" }, - "payment_link": { + "payment_method_data": { "allOf": [ { - "$ref": "#/components/schemas/PaymentLinkResponse" + "$ref": "#/components/schemas/PaymentMethodDataResponseWithBilling" } ], "nullable": true }, - "profile_id": { + "payment_token": { "type": "string", - "description": "The business profile that is associated with this payment", + "description": "Provide a reference to a stored payment method", + "example": "187282ab-40ef-47a9-9206-5099ba31e432", "nullable": true }, - "surcharge_details": { + "shipping": { "allOf": [ { - "$ref": "#/components/schemas/RequestSurchargeDetails" + "$ref": "#/components/schemas/Address" } ], "nullable": true }, - "attempt_count": { - "type": "integer", - "format": "int32", - "description": "Total number of attempts associated with this payment" - }, - "merchant_decision": { - "type": "string", - "description": "Denotes the action(approve or reject) taken by merchant in case of manual review. Manual review can occur when the transaction is marked as risky by the frm_processor, payment processor or when there is underpayment/over payment incase of crypto payment", - "nullable": true - }, - "merchant_connector_id": { - "type": "string", - "description": "Identifier of the connector ( merchant connector account ) which was chosen to make the payment", - "nullable": true - }, - "incremental_authorization_allowed": { - "type": "boolean", - "description": "If true, incremental authorization can be performed on this payment, in case the funds authorized initially fall short.", - "nullable": true - }, - "authorization_count": { - "type": "integer", - "format": "int32", - "description": "Total number of authorizations happened in an incremental_authorization payment", - "nullable": true - }, - "incremental_authorizations": { - "type": "array", - "items": { - "$ref": "#/components/schemas/IncrementalAuthorizationResponse" - }, - "description": "List of incremental authorizations happened to the payment", - "nullable": true - }, - "external_authentication_details": { + "billing": { "allOf": [ { - "$ref": "#/components/schemas/ExternalAuthenticationDetailsResponse" + "$ref": "#/components/schemas/Address" } ], "nullable": true }, - "external_3ds_authentication_attempted": { - "type": "boolean", - "description": "Flag indicating if external 3ds authentication is made or not", + "order_details": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OrderDetailsWithAmount" + }, + "description": "Information about the product , quantity and amount for connectors. (e.g. Klarna)", + "example": "[{\n \"product_name\": \"gillete creme\",\n \"quantity\": 15,\n \"amount\" : 900\n }]", "nullable": true }, - "expires_on": { + "email": { "type": "string", - "format": "date-time", - "description": "Date Time for expiry of the payment", - "example": "2022-09-10T10:11:12Z", - "nullable": true + "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 }, - "fingerprint": { + "name": { "type": "string", - "description": "Payment Fingerprint, to identify a particular card.\nIt is a 20 character long alphanumeric code.", - "nullable": true - }, - "browser_info": { - "allOf": [ - { - "$ref": "#/components/schemas/BrowserInformation" - } - ], - "nullable": true + "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 }, - "payment_method_id": { + "phone": { "type": "string", - "description": "Identifier for Payment Method used for the payment", - "nullable": true - }, - "payment_method_status": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodStatus" - } - ], - "nullable": true + "description": "The customer's phone number\nThis field will be deprecated soon. Please refer to `customer.phone` object", + "deprecated": true, + "example": "9123456789", + "nullable": true, + "maxLength": 255 }, - "updated": { + "return_url": { "type": "string", - "format": "date-time", - "description": "Date time at which payment was updated", - "example": "2022-09-10T10:11:12Z", + "description": "The URL to redirect after the completion of the operation", + "example": "https://hyperswitch.io", "nullable": true }, - "charges": { + "authentication_type": { "allOf": [ { - "$ref": "#/components/schemas/PaymentChargeResponse" + "$ref": "#/components/schemas/AuthenticationType" } ], + "default": "three_ds", "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 + "statement_descriptor_name": { + "type": "string", + "description": "For non-card charges, you can use this value as the complete description that appears on your customers’ statements. Must contain at least one letter, maximum 22 characters.", + "example": "Hyperswitch Router", + "nullable": true, + "maxLength": 255 }, - "merchant_order_reference_id": { + "statement_descriptor_suffix": { "type": "string", - "description": "Merchant's identifier for the payment/invoice. This will be sent to the connector\nif the connector provides support to accept multiple reference ids.\nIn case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference.", - "example": "Custom_Order_id_123", + "description": "Provides information about a card payment that customers see on their statements. Concatenated with the prefix (shortened descriptor) or statement descriptor that’s set on the account to form the complete statement descriptor. Maximum 255 characters for the concatenated descriptor.", + "example": "Payment for shoes purchase", "nullable": true, "maxLength": 255 }, - "order_tax_amount": { + "next_action": { "allOf": [ { - "$ref": "#/components/schemas/MinorUnit" + "$ref": "#/components/schemas/NextActionData" } ], "nullable": true }, - "connector_mandate_id": { + "cancellation_reason": { "type": "string", - "description": "Connector Identifier for the payment method", + "description": "If the payment was cancelled the reason will be provided here", "nullable": true - } - } - }, - "PaymentsDynamicTaxCalculationRequest": { - "type": "object", - "required": [ - "shipping", - "client_secret", - "payment_method_type" - ], - "properties": { - "shipping": { - "$ref": "#/components/schemas/Address" - }, - "client_secret": { - "type": "string", - "description": "Client Secret" }, - "payment_method_type": { - "$ref": "#/components/schemas/PaymentMethodType" - } - } - }, - "PaymentsDynamicTaxCalculationResponse": { - "type": "object", - "required": [ - "payment_id", - "net_amount", - "display_amount" - ], - "properties": { - "payment_id": { + "error_code": { "type": "string", - "description": "The identifier for the payment" + "description": "If there was an error while calling the connectors the code is received here", + "example": "E0001", + "nullable": true }, - "net_amount": { - "$ref": "#/components/schemas/MinorUnit" + "error_message": { + "type": "string", + "description": "If there was an error while calling the connector the error message is received here", + "example": "Failed while verifying the card", + "nullable": true }, - "order_tax_amount": { + "payment_experience": { "allOf": [ { - "$ref": "#/components/schemas/MinorUnit" + "$ref": "#/components/schemas/PaymentExperience" } ], "nullable": true }, - "shipping_cost": { + "payment_method_type": { "allOf": [ { - "$ref": "#/components/schemas/MinorUnit" + "$ref": "#/components/schemas/PaymentMethodType" } ], "nullable": true }, - "display_amount": { - "$ref": "#/components/schemas/DisplayAmountOnSdk" - } - } - }, - "PaymentsExternalAuthenticationRequest": { - "type": "object", - "required": [ - "client_secret", - "device_channel", - "threeds_method_comp_ind" - ], - "properties": { - "client_secret": { + "connector_label": { "type": "string", - "description": "Client Secret" + "description": "The connector used for this payment along with the country and business details", + "example": "stripe_US_food", + "nullable": true }, - "sdk_information": { + "business_country": { "allOf": [ { - "$ref": "#/components/schemas/SdkInformation" + "$ref": "#/components/schemas/CountryAlpha2" } ], "nullable": true }, - "device_channel": { - "$ref": "#/components/schemas/DeviceChannel" - }, - "threeds_method_comp_ind": { - "$ref": "#/components/schemas/ThreeDsCompletionIndicator" - } - } - }, - "PaymentsExternalAuthenticationResponse": { - "type": "object", - "required": [ - "trans_status", - "three_ds_requestor_url" - ], - "properties": { - "trans_status": { - "$ref": "#/components/schemas/TransactionStatus" - }, - "acs_url": { + "business_label": { "type": "string", - "description": "Access Server URL to be used for challenge submission", + "description": "The business label of merchant for this payment", "nullable": true }, - "challenge_request": { + "business_sub_label": { "type": "string", - "description": "Challenge request which should be sent to acs_url", + "description": "The business_sub_label for this payment", "nullable": true }, - "acs_reference_number": { - "type": "string", - "description": "Unique identifier assigned by the EMVCo(Europay, Mastercard and Visa)", + "allowed_payment_method_types": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PaymentMethodType" + }, + "description": "Allowed Payment Method Types for a given PaymentIntent", "nullable": true }, - "acs_trans_id": { - "type": "string", - "description": "Unique identifier assigned by the ACS to identify a single transaction", + "ephemeral_key": { + "allOf": [ + { + "$ref": "#/components/schemas/EphemeralKeyCreateResponse" + } + ], "nullable": true }, - "three_dsserver_trans_id": { - "type": "string", - "description": "Unique identifier assigned by the 3DS Server to identify a single transaction", + "manual_retry_allowed": { + "type": "boolean", + "description": "If true the payment can be retried with same or different payment method which means the confirm call can be made again.", "nullable": true }, - "acs_signed_content": { + "connector_transaction_id": { "type": "string", - "description": "Contains the JWS object created by the ACS for the ARes(Authentication Response) message", + "description": "A unique identifier for a payment provided by the connector", + "example": "993672945374576J", "nullable": true }, - "three_ds_requestor_url": { - "type": "string", - "description": "Three DS Requestor URL" - } - } - }, - "PaymentsIncrementalAuthorizationRequest": { - "type": "object", - "required": [ - "amount" - ], - "properties": { - "amount": { - "type": "integer", - "format": "int64", - "description": "The total amount including previously authorized amount and additional amount", - "example": 6540 + "frm_message": { + "allOf": [ + { + "$ref": "#/components/schemas/FrmMessage" + } + ], + "nullable": true }, - "reason": { - "type": "string", - "description": "Reason for incremental authorization", + "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. Metadata is useful for storing additional, structured information on an object.", "nullable": true - } - } - }, - "PaymentsRequest": { - "type": "object", - "properties": { - "amount": { - "type": "integer", - "format": "int64", - "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies)", - "example": 6540, - "nullable": true, - "minimum": 0 }, - "currency": { + "connector_metadata": { "allOf": [ { - "$ref": "#/components/schemas/Currency" + "$ref": "#/components/schemas/ConnectorMetadata" } ], "nullable": true }, - "amount_to_capture": { - "type": "integer", - "format": "int64", - "description": "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. Also, it must be less than or equal to the original payment account.", - "example": 6540, + "feature_metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/FeatureMetadata" + } + ], "nullable": true }, - "shipping_cost": { - "type": "integer", - "format": "int64", - "description": "The shipping cost for the payment. This is required for tax calculation in some regions.", - "example": 6540, + "reference_id": { + "type": "string", + "description": "reference(Identifier) to the payment at connector side", + "example": "993672945374576J", "nullable": true }, - "payment_id": { - "type": "string", - "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant. The value for this field can be specified in the request, it will be auto generated otherwise and returned in the API response.", - "example": "pay_mbabizu24mvu3mela5njyhpit4", - "nullable": true, - "maxLength": 30, - "minLength": 30 + "payment_link": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentLinkResponse" + } + ], + "nullable": true }, - "merchant_id": { + "profile_id": { "type": "string", - "description": "This is an identifier for the merchant account. This is inferred from the API key\nprovided during the request", - "example": "merchant_1668273825", - "nullable": true, - "maxLength": 255 + "description": "The business profile that is associated with this payment", + "nullable": true }, - "routing": { + "surcharge_details": { "allOf": [ { - "$ref": "#/components/schemas/StraightThroughAlgorithm" + "$ref": "#/components/schemas/RequestSurchargeDetails" } ], "nullable": true }, - "connector": { + "attempt_count": { + "type": "integer", + "format": "int32", + "description": "Total number of attempts associated with this payment" + }, + "merchant_decision": { + "type": "string", + "description": "Denotes the action(approve or reject) taken by merchant in case of manual review. Manual review can occur when the transaction is marked as risky by the frm_processor, payment processor or when there is underpayment/over payment incase of crypto payment", + "nullable": true + }, + "merchant_connector_id": { + "type": "string", + "description": "Identifier of the connector ( merchant connector account ) which was chosen to make the payment", + "nullable": true + }, + "incremental_authorization_allowed": { + "type": "boolean", + "description": "If true, incremental authorization can be performed on this payment, in case the funds authorized initially fall short.", + "nullable": true + }, + "authorization_count": { + "type": "integer", + "format": "int32", + "description": "Total number of authorizations happened in an incremental_authorization payment", + "nullable": true + }, + "incremental_authorizations": { "type": "array", "items": { - "$ref": "#/components/schemas/Connector" + "$ref": "#/components/schemas/IncrementalAuthorizationResponse" }, - "description": "This allows to manually select a connector with which the payment can go through.", - "example": [ - "stripe", - "adyen" - ], + "description": "List of incremental authorizations happened to the payment", "nullable": true }, - "capture_method": { + "external_authentication_details": { "allOf": [ { - "$ref": "#/components/schemas/CaptureMethod" + "$ref": "#/components/schemas/ExternalAuthenticationDetailsResponse" } ], "nullable": true }, - "authentication_type": { + "external_3ds_authentication_attempted": { + "type": "boolean", + "description": "Flag indicating if external 3ds authentication is made or not", + "nullable": true + }, + "expires_on": { + "type": "string", + "format": "date-time", + "description": "Date Time for expiry of the payment", + "example": "2022-09-10T10:11:12Z", + "nullable": true + }, + "fingerprint": { + "type": "string", + "description": "Payment Fingerprint, to identify a particular card.\nIt is a 20 character long alphanumeric code.", + "nullable": true + }, + "browser_info": { "allOf": [ { - "$ref": "#/components/schemas/AuthenticationType" + "$ref": "#/components/schemas/BrowserInformation" } ], - "default": "three_ds", "nullable": true }, - "billing": { + "payment_method_id": { + "type": "string", + "description": "Identifier for Payment Method used for the payment", + "nullable": true + }, + "payment_method_status": { "allOf": [ { - "$ref": "#/components/schemas/Address" + "$ref": "#/components/schemas/PaymentMethodStatus" } ], "nullable": true }, - "capture_on": { + "updated": { "type": "string", "format": "date-time", - "description": "A timestamp (ISO 8601 code) that determines when the payment should be captured.\nProviding this field will automatically set `capture` to true", + "description": "Date time at which payment was updated", "example": "2022-09-10T10:11:12Z", "nullable": true }, - "confirm": { - "type": "boolean", - "description": "Whether to confirm the payment (if applicable). It can be used to completely process a payment by attaching a payment method, setting `confirm=true` and `capture_method = automatic` in the *Payments/Create API* request itself.", - "default": false, - "example": true, - "nullable": true - }, - "customer": { + "split_payments": { "allOf": [ { - "$ref": "#/components/schemas/CustomerDetails" + "$ref": "#/components/schemas/SplitPaymentsResponse" } ], "nullable": true }, - "customer_id": { - "type": "string", - "description": "The identifier for the customer", - "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", - "nullable": true, - "maxLength": 64, - "minLength": 1 + "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 }, - "email": { + "merchant_order_reference_id": { "type": "string", - "description": "The customer's email address.\nThis field will be deprecated soon, use the customer object instead", - "deprecated": true, - "example": "johntest@test.com", + "description": "Merchant's identifier for the payment/invoice. This will be sent to the connector\nif the connector provides support to accept multiple reference ids.\nIn case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference.", + "example": "Custom_Order_id_123", "nullable": true, "maxLength": 255 }, - "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 + "order_tax_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true }, - "phone": { + "connector_mandate_id": { "type": "string", - "description": "The customer's phone number\nThis field will be deprecated soon, use the customer object instead", - "deprecated": true, - "example": "9123456789", - "nullable": true, - "maxLength": 255 + "description": "Connector Identifier for the payment method", + "nullable": true + } + } + }, + "PaymentsDynamicTaxCalculationRequest": { + "type": "object", + "required": [ + "shipping", + "client_secret", + "payment_method_type" + ], + "properties": { + "shipping": { + "$ref": "#/components/schemas/Address" }, - "phone_country_code": { + "client_secret": { "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 + "description": "Client Secret" }, - "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", - "example": true, - "nullable": true + "payment_method_type": { + "$ref": "#/components/schemas/PaymentMethodType" }, - "description": { + "session_id": { "type": "string", - "description": "A description for the payment", - "example": "It's my first payment request", + "description": "Session Id", "nullable": true - }, - "return_url": { + } + } + }, + "PaymentsDynamicTaxCalculationResponse": { + "type": "object", + "required": [ + "payment_id", + "net_amount", + "display_amount" + ], + "properties": { + "payment_id": { "type": "string", - "description": "The URL to which you want the user to be redirected after the completion of the payment operation", - "example": "https://hyperswitch.io", - "nullable": true + "description": "The identifier for the payment" }, - "setup_future_usage": { + "net_amount": { + "$ref": "#/components/schemas/MinorUnit" + }, + "order_tax_amount": { "allOf": [ { - "$ref": "#/components/schemas/FutureUsage" + "$ref": "#/components/schemas/MinorUnit" } ], "nullable": true }, - "payment_method_data": { + "shipping_cost": { "allOf": [ { - "$ref": "#/components/schemas/PaymentMethodDataRequest" + "$ref": "#/components/schemas/MinorUnit" } ], "nullable": true }, - "payment_method": { + "display_amount": { + "$ref": "#/components/schemas/DisplayAmountOnSdk" + } + } + }, + "PaymentsExternalAuthenticationRequest": { + "type": "object", + "required": [ + "client_secret", + "device_channel", + "threeds_method_comp_ind" + ], + "properties": { + "client_secret": { + "type": "string", + "description": "Client Secret" + }, + "sdk_information": { "allOf": [ { - "$ref": "#/components/schemas/PaymentMethod" + "$ref": "#/components/schemas/SdkInformation" } ], "nullable": true }, - "payment_token": { + "device_channel": { + "$ref": "#/components/schemas/DeviceChannel" + }, + "threeds_method_comp_ind": { + "$ref": "#/components/schemas/ThreeDsCompletionIndicator" + } + } + }, + "PaymentsExternalAuthenticationResponse": { + "type": "object", + "required": [ + "trans_status", + "three_ds_requestor_url" + ], + "properties": { + "trans_status": { + "$ref": "#/components/schemas/TransactionStatus" + }, + "acs_url": { "type": "string", - "description": "As Hyperswitch tokenises the sensitive details about the payments method, it provides the payment_token as a reference to a stored payment method, ensuring that the sensitive details are not exposed in any manner.", - "example": "187282ab-40ef-47a9-9206-5099ba31e432", + "description": "Access Server URL to be used for challenge submission", "nullable": true }, - "card_cvc": { + "challenge_request": { "type": "string", - "description": "This is used along with the payment_token field while collecting during saved card payments. This field will be deprecated soon, use the payment_method_data.card_token object instead", - "deprecated": true, + "description": "Challenge request which should be sent to acs_url", "nullable": true }, - "shipping": { - "allOf": [ - { - "$ref": "#/components/schemas/Address" - } - ], + "acs_reference_number": { + "type": "string", + "description": "Unique identifier assigned by the EMVCo(Europay, Mastercard and Visa)", "nullable": true }, - "statement_descriptor_name": { + "acs_trans_id": { "type": "string", - "description": "For non-card charges, you can use this value as the complete description that appears on your customers’ statements. Must contain at least one letter, maximum 22 characters.", - "example": "Hyperswitch Router", - "nullable": true, - "maxLength": 255 + "description": "Unique identifier assigned by the ACS to identify a single transaction", + "nullable": true }, - "statement_descriptor_suffix": { + "three_dsserver_trans_id": { "type": "string", - "description": "Provides information about a card payment that customers see on their statements. Concatenated with the prefix (shortened descriptor) or statement descriptor that’s set on the account to form the complete statement descriptor. Maximum 22 characters for the concatenated descriptor.", - "example": "Payment for shoes purchase", - "nullable": true, - "maxLength": 255 - }, - "order_details": { - "type": "array", - "items": { - "$ref": "#/components/schemas/OrderDetailsWithAmount" - }, - "description": "Use this object to capture the details about the different products for which the payment is being made. The sum of amount across different products here should be equal to the overall payment amount", - "example": "[{\n \"product_name\": \"Apple iPhone 16\",\n \"quantity\": 1,\n \"amount\" : 69000\n \"product_img_link\" : \"https://dummy-img-link.com\"\n }]", + "description": "Unique identifier assigned by the 3DS Server to identify a single transaction", "nullable": true }, - "client_secret": { + "acs_signed_content": { "type": "string", - "description": "It's a token used for client side verification.", - "example": "pay_U42c409qyHwOkWo3vK60_secret_el9ksDkiB8hi6j9N78yo", + "description": "Contains the JWS object created by the ACS for the ARes(Authentication Response) message", "nullable": true }, - "mandate_data": { - "allOf": [ - { - "$ref": "#/components/schemas/MandateData" - } - ], - "nullable": true + "three_ds_requestor_url": { + "type": "string", + "description": "Three DS Requestor URL" + } + } + }, + "PaymentsIncrementalAuthorizationRequest": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "integer", + "format": "int64", + "description": "The total amount including previously authorized amount and additional amount", + "example": 6540 }, - "customer_acceptance": { - "allOf": [ - { - "$ref": "#/components/schemas/CustomerAcceptance" - } - ], + "reason": { + "type": "string", + "description": "Reason for incremental authorization", "nullable": true + } + } + }, + "PaymentsIntentResponse": { + "type": "object", + "required": [ + "id", + "status", + "amount_details", + "client_secret", + "profile_id", + "capture_method", + "authentication_type", + "customer_id", + "customer_present", + "setup_future_usage", + "apply_mit_exemption", + "payment_link_enabled", + "request_incremental_authorization", + "expires_on", + "request_external_three_ds_authentication" + ], + "properties": { + "id": { + "type": "string", + "description": "Global Payment Id for the payment" }, - "mandate_id": { + "status": { + "$ref": "#/components/schemas/IntentStatus" + }, + "amount_details": { + "$ref": "#/components/schemas/AmountDetailsResponse" + }, + "client_secret": { + "type": "string", + "description": "It's a token used for client side verification.", + "example": "pay_U42c409qyHwOkWo3vK60_secret_el9ksDkiB8hi6j9N78yo" + }, + "profile_id": { + "type": "string", + "description": "The identifier for the profile. This is inferred from the `x-profile-id` header" + }, + "merchant_reference_id": { "type": "string", - "description": "A unique identifier to link the payment to a mandate. To do Recurring payments after a mandate has been created, pass the mandate_id instead of payment_method_data", - "example": "mandate_iwer89rnjef349dni3", + "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant.", + "example": "pay_mbabizu24mvu3mela5njyhpit4", "nullable": true, - "maxLength": 255 + "maxLength": 30, + "minLength": 30 }, - "browser_info": { - "allOf": [ - { - "$ref": "#/components/schemas/BrowserInformation" - } - ], + "routing_algorithm_id": { + "type": "string", + "description": "The routing algorithm id to be used for the payment", "nullable": true }, - "payment_experience": { + "capture_method": { + "$ref": "#/components/schemas/CaptureMethod" + }, + "authentication_type": { "allOf": [ { - "$ref": "#/components/schemas/PaymentExperience" + "$ref": "#/components/schemas/AuthenticationType" } ], - "nullable": true + "default": "no_three_ds" }, - "payment_method_type": { + "billing": { "allOf": [ { - "$ref": "#/components/schemas/PaymentMethodType" + "$ref": "#/components/schemas/Address" } ], "nullable": true }, - "business_country": { + "shipping": { "allOf": [ { - "$ref": "#/components/schemas/CountryAlpha2" + "$ref": "#/components/schemas/Address" } ], "nullable": true }, - "business_label": { + "customer_id": { "type": "string", - "description": "Business label of the merchant for this payment.\nTo be deprecated soon. Pass the profile_id instead", - "example": "food", + "description": "The identifier for the customer", + "example": "12345_cus_01926c58bc6e77c09e809964e72af8c8", + "maxLength": 64, + "minLength": 32 + }, + "customer_present": { + "$ref": "#/components/schemas/PresenceOfCustomerDuringPayment" + }, + "description": { + "type": "string", + "description": "A description for the payment", + "example": "It's my first payment request", "nullable": true }, - "merchant_connector_details": { - "allOf": [ - { - "$ref": "#/components/schemas/MerchantConnectorDetailsWrap" - } - ], + "return_url": { + "type": "string", + "description": "The URL to which you want the user to be redirected after the completion of the payment operation", + "example": "https://hyperswitch.io", + "nullable": true + }, + "setup_future_usage": { + "$ref": "#/components/schemas/FutureUsage" + }, + "apply_mit_exemption": { + "$ref": "#/components/schemas/MitExemptionRequest" + }, + "statement_descriptor": { + "type": "string", + "description": "For non-card charges, you can use this value as the complete description that appears on your customers’ statements. Must contain at least one letter, maximum 22 characters.", + "example": "Hyperswitch Router", + "nullable": true, + "maxLength": 22 + }, + "order_details": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OrderDetailsWithAmount" + }, + "description": "Use this object to capture the details about the different products for which the payment is being made. The sum of amount across different products here should be equal to the overall payment amount", + "example": "[{\n \"product_name\": \"Apple iPhone 16\",\n \"quantity\": 1,\n \"amount\" : 69000\n \"product_img_link\" : \"https://dummy-img-link.com\"\n }]", "nullable": true }, "allowed_payment_method_types": { @@ -14858,22 +15764,9 @@ "description": "Use this parameter to restrict the Payment Method Types to show for a given PaymentIntent", "nullable": true }, - "business_sub_label": { - "type": "string", - "description": "Business sub label for the payment", - "nullable": true - }, - "retry_action": { - "allOf": [ - { - "$ref": "#/components/schemas/RetryAction" - } - ], - "nullable": true - }, "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. Metadata is useful for storing additional, structured information on an object.", + "description": "Metadata is useful for storing additional, unstructured information on an object.", "nullable": true }, "connector_metadata": { @@ -14892,59 +15785,24 @@ ], "nullable": true }, - "payment_link": { - "type": "boolean", - "description": "Whether to generate the payment link for this payment or not (if applicable)", - "default": false, - "example": true, - "nullable": true + "payment_link_enabled": { + "$ref": "#/components/schemas/EnablePaymentLinkRequest" }, "payment_link_config": { "allOf": [ { - "$ref": "#/components/schemas/PaymentCreatePaymentLinkConfig" - } - ], - "nullable": true - }, - "payment_link_config_id": { - "type": "string", - "description": "Custom payment link config id set at business profile, send only if business_specific_configs is configured", - "nullable": true - }, - "profile_id": { - "type": "string", - "description": "The business profile to be used for this payment, if not passed the default business profile associated with the merchant account will be used. It is mandatory in case multiple business profiles have been set up.", - "nullable": true - }, - "surcharge_details": { - "allOf": [ - { - "$ref": "#/components/schemas/RequestSurchargeDetails" - } - ], - "nullable": true - }, - "payment_type": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentType" + "$ref": "#/components/schemas/PaymentLinkConfigRequest" } ], "nullable": true }, "request_incremental_authorization": { - "type": "boolean", - "description": "Request an incremental authorization, i.e., increase the authorized amount on a confirmed payment before you capture it.", - "nullable": true + "$ref": "#/components/schemas/RequestIncrementalAuthorization" }, - "session_expiry": { - "type": "integer", - "format": "int32", - "description": "Will be used to expire client secret after certain amount of time to be supplied in seconds\n(900) for 15 mins", - "example": 900, - "nullable": true, - "minimum": 0 + "expires_on": { + "type": "string", + "format": "date-time", + "description": "Will be used to expire client secret after certain amount of time to be supplied in seconds" }, "frm_metadata": { "type": "object", @@ -14952,38 +15810,7 @@ "nullable": true }, "request_external_three_ds_authentication": { - "type": "boolean", - "description": "Whether to perform external authentication (if applicable)", - "example": true, - "nullable": true - }, - "recurring_details": { - "allOf": [ - { - "$ref": "#/components/schemas/RecurringDetails" - } - ], - "nullable": true - }, - "charges": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentChargeRequest" - } - ], - "nullable": true - }, - "merchant_order_reference_id": { - "type": "string", - "description": "Merchant's identifier for the payment/invoice. This will be sent to the connector\nif the connector provides support to accept multiple reference ids.\nIn case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference.", - "example": "Custom_Order_id_123", - "nullable": true, - "maxLength": 255 - }, - "skip_external_tax_calculation": { - "type": "boolean", - "description": "Whether to calculate tax for this payment intent", - "nullable": true + "$ref": "#/components/schemas/External3dsAuthenticationRequest" } }, "additionalProperties": false @@ -15035,6 +15862,13 @@ "description": "The payment net amount. net_amount = amount + surcharge_details.surcharge_amount + surcharge_details.tax_amount + shipping_cost + order_tax_amount,\nIf no surcharge_details, shipping_cost, order_tax_amount, net_amount = amount", "example": 6540 }, + "shipping_cost": { + "type": "integer", + "format": "int64", + "description": "The shipping cost for the payment.", + "example": 6540, + "nullable": true + }, "amount_capturable": { "type": "integer", "format": "int64", @@ -15510,10 +16344,10 @@ "example": "2022-09-10T10:11:12Z", "nullable": true }, - "charges": { + "split_payments": { "allOf": [ { - "$ref": "#/components/schemas/PaymentChargeResponse" + "$ref": "#/components/schemas/SplitPaymentsResponse" } ], "nullable": true @@ -15547,97 +16381,149 @@ }, "PaymentsRetrieveRequest": { "type": "object", + "description": "Request for Payment Status", + "properties": { + "force_sync": { + "type": "boolean", + "description": "A boolean used to indicate if the payment status should be fetched from the connector\nIf this is set to true, the status will be fetched from the connector" + }, + "param": { + "type": "string", + "description": "These are the query params that are sent in case of redirect response.\nThese can be ingested by the connector to take necessary actions.", + "nullable": true + } + } + }, + "PaymentsRetrieveResponse": { + "type": "object", + "description": "Response for Payment Intent Confirm", "required": [ - "resource_id", - "force_sync" + "id", + "status", + "amount", + "customer_id", + "client_secret", + "created" ], "properties": { - "resource_id": { + "id": { "type": "string", - "description": "The type of ID (ex: payment intent id, payment attempt id or connector txn id)" + "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant.", + "example": "12345_pay_01926c58bc6e77c09e809964e72af8c8", + "maxLength": 64, + "minLength": 32 }, - "merchant_id": { + "status": { + "$ref": "#/components/schemas/IntentStatus" + }, + "amount": { + "$ref": "#/components/schemas/PaymentAmountDetailsResponse" + }, + "customer_id": { "type": "string", - "description": "The identifier for the Merchant Account.", + "description": "The identifier for the customer", + "example": "12345_cus_01926c58bc6e77c09e809964e72af8c8", + "maxLength": 64, + "minLength": 32 + }, + "connector": { + "type": "string", + "description": "The connector used for the payment", + "example": "stripe", "nullable": true }, - "force_sync": { - "type": "boolean", - "description": "Decider to enable or disable the connector call for retrieve request" + "client_secret": { + "type": "string", + "description": "It's a token used for client side verification." }, - "param": { + "created": { "type": "string", - "description": "The parameters passed to a retrieve request", + "format": "date-time", + "description": "Time when the payment was created", + "example": "2022-09-10T10:11:12Z" + }, + "payment_method_data": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentMethodDataResponseWithBilling" + } + ], "nullable": true }, - "connector": { + "payment_method_type": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentMethod" + } + ], + "nullable": true + }, + "payment_method_subtype": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentMethodType" + } + ], + "nullable": true + }, + "connector_transaction_id": { "type": "string", - "description": "The name of the connector", + "description": "A unique identifier for a payment provided by the connector", + "example": "993672945374576J", "nullable": true }, - "merchant_connector_details": { + "connector_reference_id": { + "type": "string", + "description": "reference(Identifier) to the payment at connector side", + "example": "993672945374576J", + "nullable": true + }, + "merchant_connector_id": { + "type": "string", + "description": "Identifier of the connector ( merchant connector account ) which was chosen to make the payment", + "nullable": true + }, + "browser_info": { + "allOf": [ + { + "$ref": "#/components/schemas/BrowserInformation" + } + ], + "nullable": true + }, + "error": { "allOf": [ { - "$ref": "#/components/schemas/MerchantConnectorDetailsWrap" + "$ref": "#/components/schemas/ErrorDetails" } ], "nullable": true }, - "client_secret": { - "type": "string", - "description": "This is a token which expires after 15 minutes, used from the client to authenticate and create sessions from the SDK", - "nullable": true - }, - "expand_captures": { - "type": "boolean", - "description": "If enabled provides list of captures linked to latest attempt", - "nullable": true - }, - "expand_attempts": { - "type": "boolean", - "description": "If enabled provides list of attempts linked to payment intent", + "shipping": { + "allOf": [ + { + "$ref": "#/components/schemas/Address" + } + ], "nullable": true - } - } - }, - "PaymentsSessionRequest": { - "type": "object", - "required": [ - "payment_id", - "client_secret", - "wallets" - ], - "properties": { - "payment_id": { - "type": "string", - "description": "The identifier for the payment" - }, - "client_secret": { - "type": "string", - "description": "This is a token which expires after 15 minutes, used from the client to authenticate and create sessions from the SDK" - }, - "wallets": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PaymentMethodType" - }, - "description": "The list of the supported wallets" }, - "merchant_connector_details": { + "billing": { "allOf": [ { - "$ref": "#/components/schemas/MerchantConnectorDetailsWrap" + "$ref": "#/components/schemas/Address" } ], "nullable": true } } }, + "PaymentsSessionRequest": { + "type": "object" + }, "PaymentsSessionResponse": { "type": "object", "required": [ "payment_id", - "client_secret", "session_token" ], "properties": { @@ -15645,10 +16531,6 @@ "type": "string", "description": "The identifier for the payment" }, - "client_secret": { - "type": "string", - "description": "This is a token which expires after 15 minutes, used from the client to authenticate and create sessions from the SDK" - }, "session_token": { "type": "array", "items": { @@ -15658,65 +16540,20 @@ } } }, - "PaymentsUpdateRequest": { + "PaymentsUpdateIntentRequest": { "type": "object", "properties": { - "amount": { - "type": "integer", - "format": "int64", - "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies)", - "example": 6540, - "nullable": true, - "minimum": 0 - }, - "currency": { + "amount_details": { "allOf": [ { - "$ref": "#/components/schemas/Currency" + "$ref": "#/components/schemas/AmountDetailsUpdate" } ], "nullable": true }, - "amount_to_capture": { - "type": "integer", - "format": "int64", - "description": "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. Also, it must be less than or equal to the original payment account.", - "example": 6540, - "nullable": true - }, - "shipping_cost": { - "type": "integer", - "format": "int64", - "description": "The shipping cost for the payment. This is required for tax calculation in some regions.", - "example": 6540, - "nullable": true - }, - "payment_id": { + "routing_algorithm_id": { "type": "string", - "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant. The value for this field can be specified in the request, it will be auto generated otherwise and returned in the API response.", - "example": "pay_mbabizu24mvu3mela5njyhpit4", - "nullable": true, - "maxLength": 30, - "minLength": 30 - }, - "routing": { - "allOf": [ - { - "$ref": "#/components/schemas/StraightThroughAlgorithm" - } - ], - "nullable": true - }, - "connector": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Connector" - }, - "description": "This allows to manually select a connector with which the payment can go through.", - "example": [ - "stripe", - "adyen" - ], + "description": "The routing algorithm id to be used for the payment", "nullable": true }, "capture_method": { @@ -15733,7 +16570,7 @@ "$ref": "#/components/schemas/AuthenticationType" } ], - "default": "three_ds", + "default": "no_three_ds", "nullable": true }, "billing": { @@ -15744,33 +16581,20 @@ ], "nullable": true }, - "confirm": { - "type": "boolean", - "description": "Whether to confirm the payment (if applicable). It can be used to completely process a payment by attaching a payment method, setting `confirm=true` and `capture_method = automatic` in the *Payments/Create API* request itself.", - "default": false, - "example": true, - "nullable": true - }, - "customer": { + "shipping": { "allOf": [ { - "$ref": "#/components/schemas/CustomerDetails" + "$ref": "#/components/schemas/Address" } ], "nullable": true }, - "customer_id": { - "type": "string", - "description": "The identifier for the customer", - "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", - "nullable": true, - "maxLength": 64, - "minLength": 1 - }, - "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", - "example": true, + "customer_present": { + "allOf": [ + { + "$ref": "#/components/schemas/PresenceOfCustomerDuringPayment" + } + ], "nullable": true }, "description": { @@ -15793,49 +16617,20 @@ ], "nullable": true }, - "payment_method_data": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodDataRequest" - } - ], - "nullable": true - }, - "payment_method": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethod" - } - ], - "nullable": true - }, - "payment_token": { - "type": "string", - "description": "As Hyperswitch tokenises the sensitive details about the payments method, it provides the payment_token as a reference to a stored payment method, ensuring that the sensitive details are not exposed in any manner.", - "example": "187282ab-40ef-47a9-9206-5099ba31e432", - "nullable": true - }, - "shipping": { + "apply_mit_exemption": { "allOf": [ { - "$ref": "#/components/schemas/Address" + "$ref": "#/components/schemas/MitExemptionRequest" } ], "nullable": true }, - "statement_descriptor_name": { + "statement_descriptor": { "type": "string", "description": "For non-card charges, you can use this value as the complete description that appears on your customers’ statements. Must contain at least one letter, maximum 22 characters.", "example": "Hyperswitch Router", "nullable": true, - "maxLength": 255 - }, - "statement_descriptor_suffix": { - "type": "string", - "description": "Provides information about a card payment that customers see on their statements. Concatenated with the prefix (shortened descriptor) or statement descriptor that’s set on the account to form the complete statement descriptor. Maximum 22 characters for the concatenated descriptor.", - "example": "Payment for shoes purchase", - "nullable": true, - "maxLength": 255 + "maxLength": 22 }, "order_details": { "type": "array", @@ -15846,54 +16641,6 @@ "example": "[{\n \"product_name\": \"Apple iPhone 16\",\n \"quantity\": 1,\n \"amount\" : 69000\n \"product_img_link\" : \"https://dummy-img-link.com\"\n }]", "nullable": true }, - "mandate_data": { - "allOf": [ - { - "$ref": "#/components/schemas/MandateData" - } - ], - "nullable": true - }, - "customer_acceptance": { - "allOf": [ - { - "$ref": "#/components/schemas/CustomerAcceptance" - } - ], - "nullable": true - }, - "browser_info": { - "allOf": [ - { - "$ref": "#/components/schemas/BrowserInformation" - } - ], - "nullable": true - }, - "payment_experience": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentExperience" - } - ], - "nullable": true - }, - "payment_method_type": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodType" - } - ], - "nullable": true - }, - "merchant_connector_details": { - "allOf": [ - { - "$ref": "#/components/schemas/MerchantConnectorDetailsWrap" - } - ], - "nullable": true - }, "allowed_payment_method_types": { "type": "array", "items": { @@ -15902,17 +16649,9 @@ "description": "Use this parameter to restrict the Payment Method Types to show for a given PaymentIntent", "nullable": true }, - "retry_action": { - "allOf": [ - { - "$ref": "#/components/schemas/RetryAction" - } - ], - "nullable": true - }, "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. Metadata is useful for storing additional, structured information on an object.", + "description": "Metadata is useful for storing additional, unstructured information on an object. This metadata will override the metadata that was passed in payments", "nullable": true }, "connector_metadata": { @@ -15923,95 +16662,53 @@ ], "nullable": true }, - "payment_link": { - "type": "boolean", - "description": "Whether to generate the payment link for this payment or not (if applicable)", - "default": false, - "example": true, - "nullable": true - }, - "payment_link_config": { + "feature_metadata": { "allOf": [ { - "$ref": "#/components/schemas/PaymentCreatePaymentLinkConfig" + "$ref": "#/components/schemas/FeatureMetadata" } ], "nullable": true }, - "payment_link_config_id": { - "type": "string", - "description": "Custom payment link config id set at business profile, send only if business_specific_configs is configured", - "nullable": true - }, - "surcharge_details": { + "payment_link_config": { "allOf": [ { - "$ref": "#/components/schemas/RequestSurchargeDetails" + "$ref": "#/components/schemas/PaymentLinkConfigRequest" } ], "nullable": true }, - "payment_type": { + "request_incremental_authorization": { "allOf": [ { - "$ref": "#/components/schemas/PaymentType" + "$ref": "#/components/schemas/RequestIncrementalAuthorization" } ], "nullable": true }, - "request_incremental_authorization": { - "type": "boolean", - "description": "Request an incremental authorization, i.e., increase the authorized amount on a confirmed payment before you capture it.", - "nullable": true - }, "session_expiry": { "type": "integer", "format": "int32", - "description": "Will be used to expire client secret after certain amount of time to be supplied in seconds\n(900) for 15 mins", + "description": "Will be used to expire client secret after certain amount of time to be supplied in seconds, if not sent it will be taken from profile config\n(900) for 15 mins", "example": 900, "nullable": true, "minimum": 0 }, "frm_metadata": { - "type": "object", - "description": "Additional data related to some frm(Fraud Risk Management) connectors", - "nullable": true - }, - "request_external_three_ds_authentication": { - "type": "boolean", - "description": "Whether to perform external authentication (if applicable)", - "example": true, - "nullable": true - }, - "recurring_details": { - "allOf": [ - { - "$ref": "#/components/schemas/RecurringDetails" - } - ], + "type": "object", + "description": "Additional data related to some frm(Fraud Risk Management) connectors", "nullable": true }, - "charges": { + "request_external_three_ds_authentication": { "allOf": [ { - "$ref": "#/components/schemas/PaymentChargeRequest" + "$ref": "#/components/schemas/External3dsAuthenticationRequest" } ], "nullable": true - }, - "merchant_order_reference_id": { - "type": "string", - "description": "Merchant's identifier for the payment/invoice. This will be sent to the connector\nif the connector provides support to accept multiple reference ids.\nIn case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference.", - "example": "Custom_Order_id_123", - "nullable": true, - "maxLength": 255 - }, - "skip_external_tax_calculation": { - "type": "boolean", - "description": "Whether to calculate tax for this payment intent", - "nullable": true } - } + }, + "additionalProperties": false }, "PayoutActionRequest": { "type": "object" @@ -17127,6 +17824,56 @@ } } }, + "PazeSessionTokenResponse": { + "type": "object", + "required": [ + "client_id", + "client_name", + "client_profile_id", + "transaction_currency_code", + "transaction_amount" + ], + "properties": { + "client_id": { + "type": "string", + "description": "Paze Client ID" + }, + "client_name": { + "type": "string", + "description": "Client Name to be displayed on the Paze screen" + }, + "client_profile_id": { + "type": "string", + "description": "Paze Client Profile ID" + }, + "transaction_currency_code": { + "$ref": "#/components/schemas/Currency" + }, + "transaction_amount": { + "type": "string", + "description": "The transaction amount", + "example": "38.02" + }, + "email_address": { + "type": "string", + "description": "Email Address", + "example": "johntest@test.com", + "nullable": true, + "maxLength": 255 + } + } + }, + "PazeWalletData": { + "type": "object", + "required": [ + "complete_response" + ], + "properties": { + "complete_response": { + "type": "string" + } + } + }, "PhoneDetails": { "type": "object", "properties": { @@ -17254,10 +18001,10 @@ }, "PresenceOfCustomerDuringPayment": { "type": "string", - "description": "Set to true to indicate that the customer is in your checkout flow during this payment, and therefore is able to authenticate. This parameter should be false when merchant's doing merchant initiated payments and customer is not present while doing the payment.", + "description": "Set to `present` to indicate that the customer is in your checkout flow during this payment, and therefore is able to authenticate. This parameter should be `absent` when merchant's doing merchant initiated payments and customer is not present while doing the payment.", "enum": [ - "Present", - "Absent" + "present", + "absent" ] }, "PrimaryBusinessDetails": { @@ -17464,7 +18211,18 @@ }, "is_network_tokenization_enabled": { "type": "boolean", - "description": "Indicates if is_network_tokenization_enabled is enabled or not.\nIf set to `true` is_network_tokenization_enabled will be checked." + "description": "Indicates if network tokenization is enabled or not." + }, + "is_click_to_pay_enabled": { + "type": "boolean", + "description": "Indicates if click to pay is enabled or not.", + "default": false, + "example": false + }, + "authentication_product_ids": { + "type": "object", + "description": "Product authentication ids", + "nullable": true } }, "additionalProperties": false @@ -17496,7 +18254,9 @@ "enable_payment_response_hash", "redirect_to_merchant_with_http_post", "is_tax_connector_enabled", - "is_network_tokenization_enabled" + "is_network_tokenization_enabled", + "should_collect_cvv_during_payment", + "is_click_to_pay_enabled" ], "properties": { "merchant_id": { @@ -17669,9 +18429,24 @@ }, "is_network_tokenization_enabled": { "type": "boolean", - "description": "Indicates if is_network_tokenization_enabled is enabled or not.\nIf set to `true` is_network_tokenization_enabled will be checked.", + "description": "Indicates if network tokenization is enabled or not.", + "default": false, + "example": false + }, + "should_collect_cvv_during_payment": { + "type": "boolean", + "description": "Indicates if CVV should be collected during payment or not." + }, + "is_click_to_pay_enabled": { + "type": "boolean", + "description": "Indicates if click to pay is enabled or not.", "default": false, "example": false + }, + "authentication_product_ids": { + "type": "object", + "description": "Product authentication ids", + "nullable": true } } }, @@ -17848,6 +18623,24 @@ "$ref": "#/components/schemas/ProcessorPaymentToken" } } + }, + { + "type": "object", + "required": [ + "type", + "data" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "network_transaction_id_and_card_details" + ] + }, + "data": { + "$ref": "#/components/schemas/NetworkTransactionIdAndCardDetails" + } + } } ], "description": "Details required for recurring payment", @@ -17855,6 +18648,16 @@ "propertyName": "type" } }, + "RecurringPaymentIntervalUnit": { + "type": "string", + "enum": [ + "year", + "month", + "day", + "hour", + "minute" + ] + }, "RedirectResponse": { "type": "object", "properties": { @@ -17868,19 +18671,18 @@ } } }, - "RefundAggregateResponse": { + "RefundErrorDetails": { "type": "object", "required": [ - "status_with_count" + "code", + "message" ], "properties": { - "status_with_count": { - "type": "object", - "description": "The list of refund status with their count", - "additionalProperties": { - "type": "integer", - "format": "int64" - } + "code": { + "type": "string" + }, + "message": { + "type": "string" } } }, @@ -18060,10 +18862,10 @@ ], "nullable": true }, - "charges": { + "split_refunds": { "allOf": [ { - "$ref": "#/components/schemas/ChargeRefunds" + "$ref": "#/components/schemas/SplitRefund" } ], "nullable": true @@ -18074,89 +18876,89 @@ "RefundResponse": { "type": "object", "required": [ - "refund_id", + "id", "payment_id", "amount", "currency", "status", - "connector" + "created_at", + "updated_at", + "connector", + "profile_id", + "merchant_connector_id" ], "properties": { - "refund_id": { + "id": { "type": "string", - "description": "Unique Identifier for the refund" + "description": "Global Refund Id for the refund" }, "payment_id": { "type": "string", "description": "The payment id against which refund is initiated" }, + "merchant_reference_id": { + "type": "string", + "description": "Unique Identifier for the Refund. This is to ensure idempotency for multiple partial refunds initiated against the same payment.", + "example": "ref_mbabizu24mvu3mela5njyhpit4", + "nullable": true, + "maxLength": 30, + "minLength": 30 + }, "amount": { "type": "integer", "format": "int64", - "description": "The refund amount, which should be less than or equal to the total 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", + "description": "The refund amount", "example": 6540, "minimum": 100 }, "currency": { - "type": "string", - "description": "The three-letter ISO currency code" + "$ref": "#/components/schemas/Currency" }, "status": { "$ref": "#/components/schemas/RefundStatus" }, "reason": { "type": "string", - "description": "An arbitrary string attached to the object. Often useful for displaying to users and your customer support executive", + "description": "An arbitrary string attached to the object", "nullable": true }, "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. Metadata is useful for storing additional, structured information on an object", + "description": "Metadata is useful for storing additional, unstructured information on an object", "nullable": true }, - "error_message": { - "type": "string", - "description": "The error message", - "nullable": true - }, - "error_code": { - "type": "string", - "description": "The code for the error", + "error_details": { + "allOf": [ + { + "$ref": "#/components/schemas/RefundErrorDetails" + } + ], "nullable": true }, "created_at": { "type": "string", "format": "date-time", - "description": "The timestamp at which refund is created", - "nullable": true + "description": "The timestamp at which refund is created" }, "updated_at": { "type": "string", "format": "date-time", - "description": "The timestamp at which refund is updated", - "nullable": true + "description": "The timestamp at which refund is updated" }, "connector": { - "type": "string", - "description": "The connector used for the refund and the corresponding payment", - "example": "stripe" + "$ref": "#/components/schemas/Connector" }, "profile_id": { "type": "string", - "description": "The id of business profile for this refund", - "nullable": true + "description": "The id of business profile for this refund" }, "merchant_connector_id": { "type": "string", - "description": "The merchant_connector_id of the processor through which this payment went through", - "nullable": true + "description": "The merchant_connector_id of the processor through which this payment went through" }, - "charges": { - "allOf": [ - { - "$ref": "#/components/schemas/ChargeRefunds" - } - ], + "connector_refund_reference_id": { + "type": "string", + "description": "The reference id of the connector for the refund", "nullable": true } } @@ -18197,6 +18999,59 @@ }, "additionalProperties": false }, + "RefundsCreateRequest": { + "type": "object", + "required": [ + "payment_id" + ], + "properties": { + "payment_id": { + "type": "string", + "description": "The payment id against which refund is initiated", + "example": "pay_mbabizu24mvu3mela5njyhpit4", + "maxLength": 30, + "minLength": 30 + }, + "merchant_reference_id": { + "type": "string", + "description": "Unique Identifier for the Refund. This is to ensure idempotency for multiple partial refunds initiated against the same payment.", + "example": "ref_mbabizu24mvu3mela5njyhpit4", + "nullable": true, + "maxLength": 30, + "minLength": 30 + }, + "amount": { + "type": "integer", + "format": "int64", + "description": "Total amount for which the refund is to be initiated. Amount for the payment in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc., If not provided, this will default to the amount_captured of the payment", + "example": 6540, + "nullable": true, + "minimum": 100 + }, + "reason": { + "type": "string", + "description": "Reason for the refund. Often useful for displaying to users and your customer support executive.", + "example": "Customer returned the product", + "nullable": true, + "maxLength": 255 + }, + "refund_type": { + "allOf": [ + { + "$ref": "#/components/schemas/RefundType" + } + ], + "default": "Instant", + "nullable": true + }, + "metadata": { + "type": "object", + "description": "Metadata is useful for storing additional, unstructured information on an object.", + "nullable": true + } + }, + "additionalProperties": false + }, "RequestIncrementalAuthorization": { "type": "string", "enum": [ @@ -18325,6 +19180,68 @@ } } }, + "ResponsePaymentMethodTypes": { + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentMethodSubtypeSpecificData" + } + ], + "nullable": true + }, + { + "type": "object", + "required": [ + "payment_method_type", + "payment_method_subtype" + ], + "properties": { + "payment_method_type": { + "$ref": "#/components/schemas/PaymentMethodType" + }, + "payment_method_subtype": { + "$ref": "#/components/schemas/PaymentMethodType" + }, + "required_fields": { + "type": "object", + "description": "Required fields for the payment_method_type.\nThis is the union of all the required fields for the payment method type enabled in all the connectors.", + "additionalProperties": { + "$ref": "#/components/schemas/RequiredFieldInfo" + }, + "nullable": true + }, + "surcharge_details": { + "allOf": [ + { + "$ref": "#/components/schemas/SurchargeDetailsResponse" + } + ], + "nullable": true + } + } + } + ] + }, + "ResponsePaymentMethodsEnabled": { + "type": "object", + "required": [ + "payment_method", + "payment_method_types" + ], + "properties": { + "payment_method": { + "$ref": "#/components/schemas/PaymentMethod" + }, + "payment_method_types": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ResponsePaymentMethodTypes" + }, + "description": "The list of payment method types enabled for a connector account" + } + } + }, "RetrieveApiKeyResponse": { "type": "object", "description": "The response body for retrieving an API Key.", @@ -18553,8 +19470,10 @@ "cybersource", "datatrans", "deutschebank", + "digitalvirgo", "dlocal", "ebanx", + "elavon", "fiserv", "fiservemea", "fiuu", @@ -18565,11 +19484,13 @@ "helcim", "iatapay", "itaubank", + "jpmorgan", "klarna", "mifinity", "mollie", "multisafepay", "nexinets", + "nexixpay", "nmi", "noon", "novalnet", @@ -18598,6 +19519,7 @@ "wise", "worldline", "worldpay", + "xendit", "zen", "plaid", "zsl" @@ -18861,28 +19783,82 @@ "option": { "$ref": "#/components/schemas/SamsungPayAmountFormat" }, - "currency_code": { - "$ref": "#/components/schemas/Currency" + "currency_code": { + "$ref": "#/components/schemas/Currency" + }, + "total": { + "type": "string", + "description": "The total amount of the transaction", + "example": "38.02" + } + } + }, + "SamsungPayAmountFormat": { + "type": "string", + "enum": [ + "FORMAT_TOTAL_PRICE_ONLY", + "FORMAT_TOTAL_ESTIMATED_AMOUNT" + ] + }, + "SamsungPayAppWalletData": { + "type": "object", + "required": [ + "3_d_s", + "payment_card_brand", + "payment_currency_type", + "payment_last4_fpan" + ], + "properties": { + "3_d_s": { + "$ref": "#/components/schemas/SamsungPayTokenData" + }, + "payment_card_brand": { + "$ref": "#/components/schemas/SamsungPayCardBrand" + }, + "payment_currency_type": { + "type": "string", + "description": "Currency type of the payment" + }, + "payment_last4_dpan": { + "type": "string", + "description": "Last 4 digits of the device specific card number", + "nullable": true }, - "total": { + "payment_last4_fpan": { "type": "string", - "description": "The total amount of the transaction", - "example": "38.02" + "description": "Last 4 digits of the card number" + }, + "merchant_ref": { + "type": "string", + "description": "Merchant reference id that was passed in the session call request", + "nullable": true + }, + "method": { + "type": "string", + "description": "Specifies authentication method used", + "nullable": true + }, + "recurring_payment": { + "type": "boolean", + "description": "Value if credential is enabled for recurring payment", + "nullable": true } } }, - "SamsungPayAmountFormat": { + "SamsungPayCardBrand": { "type": "string", "enum": [ - "FORMAT_TOTAL_PRICE_ONLY", - "FORMAT_TOTAL_ESTIMATED_AMOUNT" + "visa", + "mastercard", + "amex", + "discover", + "unknown" ] }, "SamsungPayMerchantPaymentInformation": { "type": "object", "required": [ "name", - "url", "country_code" ], "properties": { @@ -18892,7 +19868,8 @@ }, "url": { "type": "string", - "description": "Merchant domain that process payments" + "description": "Merchant domain that process payments, required for web payments", + "nullable": true }, "country_code": { "$ref": "#/components/schemas/CountryAlpha2" @@ -18970,6 +19947,27 @@ } }, "SamsungPayWalletCredentials": { + "oneOf": [ + { + "$ref": "#/components/schemas/SamsungPayWebWalletData" + }, + { + "$ref": "#/components/schemas/SamsungPayAppWalletData" + } + ] + }, + "SamsungPayWalletData": { + "type": "object", + "required": [ + "payment_credential" + ], + "properties": { + "payment_credential": { + "$ref": "#/components/schemas/SamsungPayWalletCredentials" + } + } + }, + "SamsungPayWebWalletData": { "type": "object", "required": [ "card_brand", @@ -18988,8 +19986,7 @@ "nullable": true }, "card_brand": { - "type": "string", - "description": "Brand of the payment card" + "$ref": "#/components/schemas/SamsungPayCardBrand" }, "card_last4digits": { "type": "string", @@ -19000,16 +19997,13 @@ } } }, - "SamsungPayWalletData": { - "type": "object", - "required": [ - "payment_credential" - ], - "properties": { - "payment_credential": { - "$ref": "#/components/schemas/SamsungPayWalletCredentials" - } - } + "ScaExemptionType": { + "type": "string", + "description": "SCA Exemptions types available for authentication", + "enum": [ + "low_value", + "transaction_risk_analysis" + ] }, "SdkInformation": { "type": "object", @@ -19073,6 +20067,10 @@ "properties": { "next_action": { "$ref": "#/components/schemas/NextActionCall" + }, + "order_id": { + "type": "string", + "nullable": true } } }, @@ -19212,7 +20210,8 @@ "account_holder_name", "bic", "country", - "iban" + "iban", + "reference" ], "properties": { "account_holder_name": { @@ -19229,6 +20228,10 @@ "iban": { "type": "string", "example": "123456789" + }, + "reference": { + "type": "string", + "example": "U2PVVSEV4V9Y" } } }, @@ -19360,6 +20363,48 @@ } ] }, + { + "allOf": [ + { + "$ref": "#/components/schemas/PazeSessionTokenResponse" + }, + { + "type": "object", + "required": [ + "wallet_name" + ], + "properties": { + "wallet_name": { + "type": "string", + "enum": [ + "paze" + ] + } + } + } + ] + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/ClickToPaySessionResponse" + }, + { + "type": "object", + "required": [ + "wallet_name" + ], + "properties": { + "wallet_name": { + "type": "string", + "enum": [ + "click_to_pay" + ] + } + } + } + ] + }, { "type": "object", "required": [ @@ -19430,6 +20475,60 @@ } ] }, + "SizeVariants": { + "type": "string", + "enum": [ + "cover", + "contain" + ] + }, + "SplitPaymentsRequest": { + "oneOf": [ + { + "type": "object", + "required": [ + "stripe_split_payment" + ], + "properties": { + "stripe_split_payment": { + "$ref": "#/components/schemas/StripeSplitPaymentRequest" + } + } + } + ], + "description": "Fee information for Split Payments to be charged on the payment being collected" + }, + "SplitPaymentsResponse": { + "oneOf": [ + { + "type": "object", + "required": [ + "stripe_split_payment" + ], + "properties": { + "stripe_split_payment": { + "$ref": "#/components/schemas/StripeSplitPaymentsResponse" + } + } + } + ] + }, + "SplitRefund": { + "oneOf": [ + { + "type": "object", + "required": [ + "stripe_split_refund" + ], + "properties": { + "stripe_split_refund": { + "$ref": "#/components/schemas/StripeSplitRefundRequest" + } + } + } + ], + "description": "Charge specific fields for controlling the revert of funds from either platform or connected account. Check sub-fields for more details." + }, "StraightThroughAlgorithm": { "oneOf": [ { @@ -19507,11 +20606,128 @@ "destination" ] }, + "StripeSplitPaymentRequest": { + "type": "object", + "description": "Fee information for Split Payments to be charged on the payment being collected for Stripe", + "required": [ + "charge_type", + "application_fees", + "transfer_account_id" + ], + "properties": { + "charge_type": { + "$ref": "#/components/schemas/PaymentChargeType" + }, + "application_fees": { + "type": "integer", + "format": "int64", + "description": "Platform fees to be collected on the payment", + "example": 6540 + }, + "transfer_account_id": { + "type": "string", + "description": "Identifier for the reseller's account to send the funds to" + } + }, + "additionalProperties": false + }, + "StripeSplitPaymentsResponse": { + "type": "object", + "description": "Fee information to be charged on the payment being collected", + "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", + "example": 6540 + }, + "transfer_account_id": { + "type": "string", + "description": "Identifier for the reseller's account where the funds were transferred" + } + } + }, + "StripeSplitRefundRequest": { + "type": "object", + "description": "Charge specific fields for controlling the revert of funds from either platform or connected account for Stripe. Check sub-fields for more details.", + "properties": { + "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 + } + }, + "additionalProperties": false + }, + "SupportedPaymentMethod": { + "type": "object", + "required": [ + "payment_method", + "payment_method_type", + "mandates", + "refunds", + "supported_capture_methods" + ], + "properties": { + "payment_method": { + "$ref": "#/components/schemas/PaymentMethod" + }, + "payment_method_type": { + "$ref": "#/components/schemas/PaymentMethodType" + }, + "mandates": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "refunds": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "supported_capture_methods": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaptureMethod" + } + }, + "supported_countries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CountryAlpha2" + }, + "uniqueItems": true, + "nullable": true + }, + "supported_currencies": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Currency" + }, + "uniqueItems": true, + "nullable": true + } + } + }, "SurchargeCalculationOverride": { "type": "string", "enum": [ - "Skip", - "Calculate" + "skip", + "calculate" ] }, "SurchargeDetailsResponse": { @@ -19520,8 +20736,7 @@ "surcharge", "display_surcharge_amount", "display_tax_on_surcharge_amount", - "display_total_surcharge_amount", - "display_final_amount" + "display_total_surcharge_amount" ], "properties": { "surcharge": { @@ -19549,11 +20764,6 @@ "type": "number", "format": "double", "description": "sum of display_surcharge_amount and display_tax_on_surcharge_amount" - }, - "display_final_amount": { - "type": "number", - "format": "double", - "description": "sum of original amount," } } }, @@ -19618,8 +20828,8 @@ "TaxCalculationOverride": { "type": "string", "enum": [ - "Skip", - "Calculate" + "skip", + "calculate" ] }, "ThirdPartySdkSessionResponse": { @@ -20534,6 +21744,17 @@ } } }, + { + "type": "object", + "required": [ + "paze" + ], + "properties": { + "paze": { + "$ref": "#/components/schemas/PazeWalletData" + } + } + }, { "type": "object", "required": [ @@ -20676,7 +21897,8 @@ } } } - ] + ], + "description": "Hyperswitch supports SDK integration with Apple Pay and Google Pay wallets. For other wallets, we integrate with their respective connectors, redirecting the customer to the connector for wallet payments. As a result, we don’t receive any payment method data in the confirm call for payments made through other wallets." }, "WeChatPay": { "type": "object" @@ -20752,7 +21974,7 @@ "type": "apiKey", "in": "header", "name": "api-key", - "description": "Admin API keys allow you to perform some privileged actions such as creating a merchant account and Merchant Connector account." + "description": "Admin API keys allow you to perform some privileged actions such as creating a merchant account and Connector account." }, "api_key": { "type": "apiKey", diff --git a/api-reference-v2/rust_locker_open_api_spec.yml b/api-reference-v2/rust_locker_open_api_spec.yml index 729886d9cd0d..17a19fec44da 100644 --- a/api-reference-v2/rust_locker_open_api_spec.yml +++ b/api-reference-v2/rust_locker_open_api_spec.yml @@ -2,16 +2,16 @@ openapi: "3.0.2" info: title: Tartarus - OpenAPI 3.0 description: |- - This the the open API 3.0 specification for the card locker. + This is the OpenAPI 3.0 specification for the card locker. This is used by the [hyperswitch](https://github.com/juspay/hyperswitch) for storing card information securely. version: "1.0" tags: - name: Key Custodian description: API used to initialize the locker after deployment. - name: Data - description: CRUD APIs to for working with data to be stored in the locker + description: CRUD APIs for working with data to be stored in the locker - name: Cards - description: CRUD APIs to for working with cards data to be stored in the locker (deprecated) + description: CRUD APIs for working with cards data to be stored in the locker (deprecated) paths: /custodian/key1: post: @@ -39,7 +39,7 @@ paths: tags: - Key Custodian summary: Provide Key 2 - description: Provide the first key to unlock the locker + description: Provide the second key to unlock the locker operationId: setKey2 requestBody: description: Provide key 2 to unlock the locker diff --git a/api-reference/api-reference/api-key/api-key--create.mdx b/api-reference/api-reference/api-key/api-key--create.mdx index 977d4b928518..663ec4e66458 100644 --- a/api-reference/api-reference/api-key/api-key--create.mdx +++ b/api-reference/api-reference/api-key/api-key--create.mdx @@ -1,3 +1,3 @@ --- -openapi: post /api_keys/{merchant_id) +openapi: post /api_keys/{merchant_id} --- \ No newline at end of file diff --git a/api-reference/api-reference/api-key/api-key--revoke.mdx b/api-reference/api-reference/api-key/api-key--revoke.mdx index d95f088533e5..62bbf0203219 100644 --- a/api-reference/api-reference/api-key/api-key--revoke.mdx +++ b/api-reference/api-reference/api-key/api-key--revoke.mdx @@ -1,3 +1,3 @@ --- -openapi: delete /api_keys/{merchant_id)/{key_id} +openapi: delete /api_keys/{merchant_id}/{key_id} --- \ No newline at end of file diff --git a/api-reference/api-reference/organization/organization--retrieve.mdx b/api-reference/api-reference/organization/organization--retrieve.mdx index 8495797dcc4d..d5cd76a472da 100644 --- a/api-reference/api-reference/organization/organization--retrieve.mdx +++ b/api-reference/api-reference/organization/organization--retrieve.mdx @@ -1,3 +1,3 @@ --- -openapi: get /organization/{organization_id} +openapi: get /organization/{id} --- \ No newline at end of file diff --git a/api-reference/api-reference/organization/organization--update.mdx b/api-reference/api-reference/organization/organization--update.mdx index 202020c7ca64..e51cc5690754 100644 --- a/api-reference/api-reference/organization/organization--update.mdx +++ b/api-reference/api-reference/organization/organization--update.mdx @@ -1,3 +1,3 @@ --- -openapi: put /organization/{organization_id} +openapi: put /organization/{id} --- \ No newline at end of file diff --git a/api-reference/api-reference/payments/payments--post-session-tokens.mdx b/api-reference/api-reference/payments/payments--post-session-tokens.mdx new file mode 100644 index 000000000000..6c327886c529 --- /dev/null +++ b/api-reference/api-reference/payments/payments--post-session-tokens.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /payments/{payment_id}/post_session_tokens +--- \ No newline at end of file diff --git a/api-reference/api-reference/relay/relay--retrieve.mdx b/api-reference/api-reference/relay/relay--retrieve.mdx new file mode 100644 index 000000000000..d65e62d31d74 --- /dev/null +++ b/api-reference/api-reference/relay/relay--retrieve.mdx @@ -0,0 +1,3 @@ +--- +openapi: openapi_spec get /relay/{relay_id} +--- \ No newline at end of file diff --git a/api-reference/api-reference/relay/relay.mdx b/api-reference/api-reference/relay/relay.mdx new file mode 100644 index 000000000000..a6b5962740a6 --- /dev/null +++ b/api-reference/api-reference/relay/relay.mdx @@ -0,0 +1,3 @@ +--- +openapi: openapi_spec post /relay +--- \ No newline at end of file diff --git a/api-reference/api-reference/routing/routing--activate-config.mdx b/api-reference/api-reference/routing/routing--activate-config.mdx index 990723a90c71..12e39e519e90 100644 --- a/api-reference/api-reference/routing/routing--activate-config.mdx +++ b/api-reference/api-reference/routing/routing--activate-config.mdx @@ -1,3 +1,3 @@ --- -openapi: post /routing/{algorithm_id}/activate +openapi: post /routing/{routing_algorithm_id}/activate --- \ No newline at end of file diff --git a/api-reference/api-reference/routing/routing--retrieve.mdx b/api-reference/api-reference/routing/routing--retrieve.mdx index f39e44c824c3..3c5d31cb2fad 100644 --- a/api-reference/api-reference/routing/routing--retrieve.mdx +++ b/api-reference/api-reference/routing/routing--retrieve.mdx @@ -1,3 +1,3 @@ --- -openapi: get /routing/{algorithm_id} +openapi: get /routing/{routing_algorithm_id} --- \ No newline at end of file diff --git a/api-reference/essentials/error_codes.mdx b/api-reference/essentials/error_codes.mdx index 2f633e9cffed..e2c1e53d1111 100644 --- a/api-reference/essentials/error_codes.mdx +++ b/api-reference/essentials/error_codes.mdx @@ -2,8 +2,14 @@ title: Error Codes --- -Hyperswitch uses Error codes, types, and messages to communicate errors during API calls. There are three types of error codes: +Hyperswitch uses error codes, types, and messages to communicate errors during API calls. There are two main types of error codes: Error Codes and Unified Error Codes. +1. **Error Codes** refer to the error code sent by the connector. +2. **Unified Error Codes** refer to the generic error code sent by the Hyperswitch server, based on the connector's error code. Hyperswitch groups the different error codes from connectors into more generic Unified Error Codes for structured relay of PSP errors, helping merchants derive patterns and determine the next steps for ongoing transactions. +There are four types of Error Codes and five types of Unified Error Codes. + +**1. Error Codes:** +The section below contains all the error codes and their corresponding error messages. | Error Code | Type | Description | | ---------- | --------------------- | ------------------------------------------------------------ | | IR | Invalid Request Error | Error caused due to invalid fields and values in API request | @@ -31,7 +37,7 @@ Hyperswitch uses Error codes, types, and messages to communicate errors during A | IR_15 | 400 | invalid_request_error | Invalid Ephemeral Key for the customer | Please pass the right Ephemeral key for the customer | | IR_16 | 400 | invalid_request_error | “message” | Typically used when information involving multiple fields or previously provided information doesn’t satisfy a condition. Refer to our API documentation for required fields and format | | IR_17 | 401 | invalid_request_error | Access forbidden, an invalid JWT token was used | Provide a valid JWT token to access the APIs | -| IR_18 | 401 | invalid_request_error | "message" | The user is not authorised to update the customer, Contact Org. Admin for the appropriate access. | +| IR_18 | 401 | invalid_request_error | "message" | The user is not authorised to update the customer, Contact Org. Admin for the appropriate access. | | IR_19 | 400 | invalid_request_error | "message" | Please check and retry with correct details. Refer to our API documentation | | IR_20 | 400 | invalid_request_error | "flow" not supported by the "connector" | Requested flow is not supported for this Connector. | | IR_21 | 400 | invalid_request_error | Missing required params | Please add the required params in the request. Refer to our API documentation | @@ -74,4 +80,16 @@ Hyperswitch uses Error codes, types, and messages to communicate errors during A | WE_03 | 500 | router_error | There was some issue processing the webhook | Please try again later. If the issue persists, contact Hyperswitch support. | | WE_04 | 404 | object_not_found | Webhook resource not found | Ensure the webhook URL is correct and the resource exists. | | WE_05 | 400 | invalid_request_error | Unable to process the webhook body | Ensure the webhook body is correctly formatted and try again. | -| WE_06 | 400 | invalid_request_error | Merchant Secret set by merchant for webhook source verification is invalid | Verify the Merchant Secret, then try again. | \ No newline at end of file +| WE_06 | 400 | invalid_request_error | Merchant Secret set by merchant for webhook source verification is invalid | Verify the Merchant Secret, then try again. | + + +**2. Unified Error codes:** +The section below contains all the unified error codes and their corresponding error messages. + +| Unified Error Code | Unified Error Categorisation | Unified Error message | +| ------------------ | ------------------------------------- | ----------------------------------- | +| UE_1000 | Customer Error | Issue with payment method details. | +| UE_2000 | Connector Declines | Issue with Configurations. | +| UE_3000 | Connector Error | Technical issue with PSP. | +| UE_4000 | Integration Error | Issue in the integration. | +| UE_9000 | Others | Something went wrong. | \ No newline at end of file diff --git a/api-reference/essentials/webhooks.mdx b/api-reference/essentials/webhooks.mdx deleted file mode 100644 index 20ce3518ea72..000000000000 --- a/api-reference/essentials/webhooks.mdx +++ /dev/null @@ -1,45 +0,0 @@ -# Webhooks - -Webhooks are HTTP-based real-time push notifications that Hyperswitch would use for instant status communication to your server. Webhooks are vital in payments for the following reasons: - -- Preventing merchants from losing business due to delayed status communication (say, in case of flight or movie reservations where there is a need for instant payment confirmation). -- Prevent payment reconciliation issues where payments change from “Failed” to “Succeeded”. -- Providing the best payment experience for the end-user by instantly communicating payment status and fulfilling the purchase. - -## Configuring Webhooks - -You would need to set up a dedicated HTTPS or HTTP endpoint on your server with a URL as a webhook listener that will receive push notifications in the form of a POST request with JSON payload from the Hyperswitch server - Update the above endpoint on your Hyperswitch dashboard under Settings -> Webhooks -In order for Hyperswitch to receive updates from the connectors you have selected, you would need to update Hyperswitch’s corresponding endpoints on your respective connector dashboard instead of your webhook endpoints - - -Hyperswitch’s webhook endpoint format is as follows: - -| Environment | Webhook Endpoint | -| ----------- | ---------------- | -| Sandbox | sandbox.hyperswitch.io/webhooks/`{merchant_id}`/`{connector_name}` | -| Production | api.hyperswitch.io/webhooks/`{merchant_id}`/`{connector_name}`| - -## Handling Webhooks - -- **Select the events for Webhooks:** On the same page on the dashboard, select the events for which you would like to receive notifications. Currently, Webhooks are available on Hyperswitch for the following events: - - 1. payment_succeeded - 2. payment_failed - 3. payment_processing - 4. action_required - 5. refund_succeeded - 6. refund_failed - 7. dispute_opened - 8. dispute_expired - 9. dispute_accepted - 10. dispute_cancelled - 11. dispute_challenged - 12. dispute_won - 13. dispute_lost - -Click [**here**](https://juspay-78.mintlify.app/api-reference/schemas/outgoing--webhook) to see the webhook payload your endpoint would need to parse for each of the above events - -- **Return a 2xx response:** Your server must return a successful 2xx response on successful receipt of webhooks. - -- **Retries:** In case of 3xx, 4xx, or 5xx response or no response from your endpoint for webhooks, Hyperswitch has a retry mechanism that tries sending the webhooks again up to 3 times before marking the event as failed. diff --git a/api-reference/logo/dark.svg b/api-reference/logo/dark.svg index fbf0d89d4106..f07be0cea141 100644 --- a/api-reference/logo/dark.svg +++ b/api-reference/logo/dark.svg @@ -1,29 +1,21 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/api-reference/logo/light.svg b/api-reference/logo/light.svg index c951a909dd49..66b2c279d06f 100644 --- a/api-reference/logo/light.svg +++ b/api-reference/logo/light.svg @@ -1,29 +1,21 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/api-reference/mint.json b/api-reference/mint.json index 03a76fed1830..5e9c9c6d36c6 100644 --- a/api-reference/mint.json +++ b/api-reference/mint.json @@ -32,7 +32,6 @@ { "group": "Essentials", "pages": [ - "essentials/webhooks", "essentials/error_codes", "essentials/rate_limit", "essentials/go-live" @@ -235,6 +234,13 @@ "api-reference/routing/routing--activate-config" ] }, + { + "group": "Relay", + "pages": [ + "api-reference/relay/relay", + "api-reference/relay/relay--retrieve" + ] + }, { "group": "Schemas", "pages": ["api-reference/schemas/outgoing--webhook"] diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index d91dbb9f8252..186aa6dc9c3b 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -867,7 +867,6 @@ "Payments" ], "summary": "Payments - Complete Authorize", - "description": "\n", "operationId": "Complete Authorize a Payment", "parameters": [ { @@ -912,6 +911,164 @@ ] } }, + "/payments/{payment_id}/post_session_tokens": { + "post": { + "tags": [ + "Payments" + ], + "summary": "Payments - Post Session Tokens", + "operationId": "Create Post Session Tokens for a Payment", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentsPostSessionTokensRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Post Session Token is done", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentsPostSessionTokensResponse" + } + } + } + }, + "400": { + "description": "Missing mandatory fields" + } + }, + "security": [ + { + "publishable_key": [] + } + ] + } + }, + "/relay": { + "post": { + "tags": [ + "Relay" + ], + "summary": "Relay - Create", + "description": "Creates a relay request.", + "operationId": "Relay Request", + "parameters": [ + { + "name": "X-Profile-Id", + "in": "header", + "description": "Profile ID for authentication", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "X-Idempotency-Key", + "in": "header", + "description": "Idempotency Key for relay request", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RelayRequest" + }, + "examples": { + "Create a relay request": { + "value": { + "connector_id": "mca_5apGeP94tMts6rg3U3kR", + "connector_resource_id": "7256228702616471803954", + "data": { + "refund": { + "amount": 6540, + "currency": "USD" + } + }, + "type": "refund" + } + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Relay request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RelayResponse" + } + } + } + }, + "400": { + "description": "Invalid data" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/relay/{relay_id}": { + "get": { + "tags": [ + "Relay" + ], + "summary": "Relay - Retrieve", + "description": "Retrieves a relay details.", + "operationId": "Retrieve a Relay details", + "parameters": [ + { + "name": "X-Profile-Id", + "in": "header", + "description": "Profile ID for authentication", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Relay Retrieved", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RelayResponse" + } + } + } + }, + "404": { + "description": "Relay details was not found" + } + }, + "security": [ + { + "api_key": [] + }, + { + "ephemeral_key": [] + } + ] + } + }, "/refunds": { "post": { "tags": [ @@ -1078,7 +1235,7 @@ "Refunds" ], "summary": "Refunds - List", - "description": "Lists all the refunds associated with the merchant or a payment_id if payment_id is not provided", + "description": "Lists all the refunds associated with the merchant, or for a specific payment if payment_id is provided", "operationId": "List all Refunds", "requestBody": { "content": { @@ -1156,7 +1313,7 @@ ] } }, - "/organization/{organization_id}": { + "/organization/{id}": { "get": { "tags": [ "Organization" @@ -1166,7 +1323,7 @@ "operationId": "Retrieve an Organization", "parameters": [ { - "name": "organization_id", + "name": "id", "in": "path", "description": "The unique identifier for the Organization", "required": true, @@ -1205,7 +1362,7 @@ "operationId": "Update an Organization", "parameters": [ { - "name": "organization_id", + "name": "id", "in": "path", "description": "The unique identifier for the Organization", "required": true, @@ -2228,7 +2385,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CustomerRequest" + "$ref": "#/components/schemas/CustomerUpdateRequest" }, "examples": { "Update name and email of a customer": { @@ -3884,14 +4041,98 @@ ] } }, - "/account/:account_id/business_profile/:profile_id/dynamic_routing/success_based/toggle": { + "/account/{account_id}/business_profile/{profile_id}/dynamic_routing/success_based/config/{algorithm_id}": { + "patch": { + "tags": [ + "Routing" + ], + "summary": "Routing - Update success based dynamic routing config for profile", + "description": "Update success based dynamic routing algorithm", + "operationId": "Update success based dynamic routing configs", + "parameters": [ + { + "name": "account_id", + "in": "path", + "description": "Merchant id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "profile_id", + "in": "path", + "description": "Profile id under which Dynamic routing needs to be toggled", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "algorithm_id", + "in": "path", + "description": "Success based routing algorithm id which was last activated to update the config", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DynamicRoutingFeatures" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Routing Algorithm updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingDictionaryRecord" + } + } + } + }, + "400": { + "description": "Update body is malformed" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Resource missing" + }, + "422": { + "description": "Unprocessable request" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "api_key": [] + }, + { + "jwt_key": [] + } + ] + } + }, + "/account/{account_id}/business_profile/{profile_id}/dynamic_routing/success_based/toggle": { "post": { "tags": [ "Routing" ], "summary": "Routing - Toggle success based dynamic routing for profile", "description": "Create a success based dynamic routing algorithm", - "operationId": "Toggle success based dynamic routing algprithm", + "operationId": "Toggle success based dynamic routing algorithm", "parameters": [ { "name": "account_id", @@ -3912,12 +4153,12 @@ } }, { - "name": "status", + "name": "enable", "in": "query", - "description": "Boolean value for mentioning the expected state of dynamic routing", + "description": "Feature to enable for success based routing", "required": true, "schema": { - "type": "boolean" + "$ref": "#/components/schemas/DynamicRoutingFeatures" } } ], @@ -3958,14 +4199,14 @@ ] } }, - "/account/:account_id/business_profile/:profile_id/dynamic_routing/success_based/config/:algorithm_id": { - "patch": { + "/account/{account_id}/business_profile/{profile_id}/dynamic_routing/elimination/toggle": { + "post": { "tags": [ "Routing" ], - "summary": "Routing - Update config for success based dynamic routing", - "description": "Update config for success based dynamic routing", - "operationId": "Update configs for success based dynamic routing algorithm", + "summary": "Routing - Toggle elimination routing for profile", + "description": "Create a elimination based dynamic routing algorithm", + "operationId": "Toggle elimination routing algorithm", "parameters": [ { "name": "account_id", @@ -3979,35 +4220,25 @@ { "name": "profile_id", "in": "path", - "description": "The unique identifier for a profile", + "description": "Profile id under which Dynamic routing needs to be toggled", "required": true, "schema": { "type": "string" } }, { - "name": "algorithm_id", - "in": "path", - "description": "The unique identifier for routing algorithm", + "name": "enable", + "in": "query", + "description": "Feature to enable for success based routing", "required": true, "schema": { - "type": "string" + "$ref": "#/components/schemas/DynamicRoutingFeatures" } } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessBasedRoutingConfig" - } - } - }, - "required": true - }, "responses": { "200": { - "description": "Routing Algorithm updated", + "description": "Routing Algorithm created", "content": { "application/json": { "schema": { @@ -4034,7 +4265,10 @@ }, "security": [ { - "admin_api_key": [] + "api_key": [] + }, + { + "jwt_key": [] } ] } @@ -5768,29 +6002,186 @@ } ], "nullable": true + }, + "recurring_payment_request": { + "allOf": [ + { + "$ref": "#/components/schemas/ApplePayRecurringPaymentRequest" + } + ], + "nullable": true } } }, - "ApplePayRedirectData": { - "type": "object" + "ApplePayPaymentTiming": { + "type": "string", + "enum": [ + "immediate", + "recurring" + ] }, - "ApplePaySessionResponse": { - "oneOf": [ - { - "$ref": "#/components/schemas/ThirdPartySdkSessionResponse" + "ApplePayRecurringDetails": { + "type": "object", + "required": [ + "payment_description", + "regular_billing", + "management_url" + ], + "properties": { + "payment_description": { + "type": "string", + "description": "A description of the recurring payment that Apple Pay displays to the user in the payment sheet" }, - { - "$ref": "#/components/schemas/NoThirdPartySdkSessionResponse" + "regular_billing": { + "$ref": "#/components/schemas/ApplePayRegularBillingDetails" }, - { - "type": "object", - "default": null, + "billing_agreement": { + "type": "string", + "description": "A localized billing agreement that the payment sheet displays to the user before the user authorizes the payment", "nullable": true + }, + "management_url": { + "type": "string", + "description": "A URL to a web page where the user can update or delete the payment method for the recurring payment", + "example": "https://hyperswitch.io" } - ] + } }, - "ApplePayShippingContactFields": { - "type": "array", + "ApplePayRecurringPaymentRequest": { + "type": "object", + "required": [ + "payment_description", + "regular_billing", + "management_url" + ], + "properties": { + "payment_description": { + "type": "string", + "description": "A description of the recurring payment that Apple Pay displays to the user in the payment sheet" + }, + "regular_billing": { + "$ref": "#/components/schemas/ApplePayRegularBillingRequest" + }, + "billing_agreement": { + "type": "string", + "description": "A localized billing agreement that the payment sheet displays to the user before the user authorizes the payment", + "nullable": true + }, + "management_url": { + "type": "string", + "description": "A URL to a web page where the user can update or delete the payment method for the recurring payment", + "example": "https://hyperswitch.io" + } + } + }, + "ApplePayRedirectData": { + "type": "object" + }, + "ApplePayRegularBillingDetails": { + "type": "object", + "required": [ + "label" + ], + "properties": { + "label": { + "type": "string", + "description": "The label that Apple Pay displays to the user in the payment sheet with the recurring details" + }, + "recurring_payment_start_date": { + "type": "string", + "format": "date-time", + "description": "The date of the first payment", + "example": "2023-09-10T23:59:59Z", + "nullable": true + }, + "recurring_payment_end_date": { + "type": "string", + "format": "date-time", + "description": "The date of the final payment", + "example": "2023-09-10T23:59:59Z", + "nullable": true + }, + "recurring_payment_interval_unit": { + "allOf": [ + { + "$ref": "#/components/schemas/RecurringPaymentIntervalUnit" + } + ], + "nullable": true + }, + "recurring_payment_interval_count": { + "type": "integer", + "format": "int32", + "description": "The number of interval units that make up the total payment interval", + "nullable": true + } + } + }, + "ApplePayRegularBillingRequest": { + "type": "object", + "required": [ + "amount", + "label", + "payment_timing" + ], + "properties": { + "amount": { + "type": "string", + "description": "The amount of the recurring payment", + "example": "38.02" + }, + "label": { + "type": "string", + "description": "The label that Apple Pay displays to the user in the payment sheet with the recurring details" + }, + "payment_timing": { + "$ref": "#/components/schemas/ApplePayPaymentTiming" + }, + "recurring_payment_start_date": { + "type": "string", + "format": "date-time", + "description": "The date of the first payment", + "nullable": true + }, + "recurring_payment_end_date": { + "type": "string", + "format": "date-time", + "description": "The date of the final payment", + "nullable": true + }, + "recurring_payment_interval_unit": { + "allOf": [ + { + "$ref": "#/components/schemas/RecurringPaymentIntervalUnit" + } + ], + "nullable": true + }, + "recurring_payment_interval_count": { + "type": "integer", + "format": "int32", + "description": "The number of interval units that make up the total payment interval", + "nullable": true + } + } + }, + "ApplePaySessionResponse": { + "oneOf": [ + { + "$ref": "#/components/schemas/ThirdPartySdkSessionResponse" + }, + { + "$ref": "#/components/schemas/NoThirdPartySdkSessionResponse" + }, + { + "type": "object", + "default": null, + "nullable": true + } + ] + }, + "ApplePayShippingContactFields": { + "type": "array", "items": { "$ref": "#/components/schemas/ApplePayAddressParameters" } @@ -5968,7 +6359,9 @@ "enum": [ "threedsecureio", "netcetera", - "gpayments" + "gpayments", + "ctp_mastercard", + "unified_authentication_service" ] }, "AuthenticationStatus": { @@ -6185,6 +6578,27 @@ ], "description": "Masked payout method details for bank payout method" }, + "BankCodeResponse": { + "type": "object", + "required": [ + "bank_name", + "eligible_connectors" + ], + "properties": { + "bank_name": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BankNames" + } + }, + "eligible_connectors": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "BankDebitAdditionalData": { "oneOf": [ { @@ -6455,6 +6869,20 @@ } ] }, + "BankDebitTypes": { + "type": "object", + "required": [ + "eligible_connectors" + ], + "properties": { + "eligible_connectors": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "BankHolderType": { "type": "string", "enum": [ @@ -6763,7 +7191,7 @@ }, "bank_account_bic": { "type": "string", - "description": "Bank account details for Giropay\nBank account bic code", + "description": "Bank account bic code", "nullable": true }, "bank_account_iban": { @@ -7678,6 +8106,25 @@ } ] }, + "BankTransferTypes": { + "type": "object", + "required": [ + "eligible_connectors" + ], + "properties": { + "eligible_connectors": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The list of eligible connectors for a given payment experience", + "example": [ + "stripe", + "adyen" + ] + } + } + }, "BankType": { "type": "string", "enum": [ @@ -7880,6 +8327,21 @@ "type": "string", "description": "User-agent of the browser", "nullable": true + }, + "os_type": { + "type": "string", + "description": "The os type of the client device", + "nullable": true + }, + "os_version": { + "type": "string", + "description": "The os version of the client device", + "nullable": true + }, + "device_model": { + "type": "string", + "description": "The device model of the client", + "nullable": true } } }, @@ -7963,6 +8425,11 @@ "description": "A list of allowed domains (glob patterns) where this link can be embedded / opened from", "uniqueItems": true, "nullable": true + }, + "branding_visibility": { + "type": "boolean", + "description": "Toggle for HyperSwitch branding visibility", + "nullable": true } } } @@ -8001,7 +8468,8 @@ "automatic", "manual", "manual_multiple", - "scheduled" + "scheduled", + "sequential_automatic" ] }, "CaptureResponse": { @@ -8403,6 +8871,41 @@ "Maestro" ] }, + "CardNetworkTypes": { + "type": "object", + "required": [ + "eligible_connectors" + ], + "properties": { + "card_network": { + "allOf": [ + { + "$ref": "#/components/schemas/CardNetwork" + } + ], + "nullable": true + }, + "surcharge_details": { + "allOf": [ + { + "$ref": "#/components/schemas/SurchargeDetailsResponse" + } + ], + "nullable": true + }, + "eligible_connectors": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The list of eligible connectors for a given card network", + "example": [ + "stripe", + "adyen" + ] + } + } + }, "CardPayout": { "type": "object", "required": [ @@ -8622,6 +9125,73 @@ } } }, + "ClickToPaySessionResponse": { + "type": "object", + "required": [ + "dpa_id", + "dpa_name", + "locale", + "card_brands", + "acquirer_bin", + "acquirer_merchant_id", + "merchant_category_code", + "merchant_country_code", + "transaction_amount", + "transaction_currency_code" + ], + "properties": { + "dpa_id": { + "type": "string" + }, + "dpa_name": { + "type": "string" + }, + "locale": { + "type": "string" + }, + "card_brands": { + "type": "array", + "items": { + "type": "string" + } + }, + "acquirer_bin": { + "type": "string" + }, + "acquirer_merchant_id": { + "type": "string" + }, + "merchant_category_code": { + "type": "string" + }, + "merchant_country_code": { + "type": "string" + }, + "transaction_amount": { + "type": "string", + "example": "38.02" + }, + "transaction_currency_code": { + "$ref": "#/components/schemas/Currency" + }, + "phone_number": { + "type": "string", + "example": "9123456789", + "nullable": true, + "maxLength": 255 + }, + "email": { + "type": "string", + "example": "johntest@test.com", + "nullable": true, + "maxLength": 255 + }, + "phone_country_code": { + "type": "string", + "nullable": true + } + } + }, "Comparison": { "type": "object", "description": "Represents a single comparison condition.", @@ -8689,11 +9259,14 @@ "checkout", "coinbase", "cryptopay", + "ctp_mastercard", "cybersource", "datatrans", "deutschebank", + "digitalvirgo", "dlocal", "ebanx", + "elavon", "fiserv", "fiservemea", "fiuu", @@ -8705,12 +9278,14 @@ "helcim", "iatapay", "itaubank", + "jpmorgan", "klarna", "mifinity", "mollie", "multisafepay", "netcetera", "nexinets", + "nexixpay", "nmi", "noon", "novalnet", @@ -8746,34 +9321,71 @@ "zsl" ] }, - "ConnectorMetadata": { + "ConnectorFeatureMatrixResponse": { "type": "object", - "description": "Some connectors like Apple Pay, Airwallex and Noon might require some additional information, find specific details in the child attributes below.", + "required": [ + "name", + "supported_payment_methods" + ], "properties": { - "apple_pay": { - "allOf": [ - { - "$ref": "#/components/schemas/ApplepayConnectorMetadataRequest" - } - ], - "nullable": true + "name": { + "type": "string" }, - "airwallex": { - "allOf": [ - { - "$ref": "#/components/schemas/AirwallexData" - } - ], + "description": { + "type": "string", "nullable": true }, - "noon": { + "category": { "allOf": [ { - "$ref": "#/components/schemas/NoonData" + "$ref": "#/components/schemas/PaymentConnectorCategory" } ], "nullable": true - } + }, + "supported_payment_methods": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SupportedPaymentMethod" + } + }, + "supported_webhook_flows": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EventClass" + }, + "nullable": true + } + } + }, + "ConnectorMetadata": { + "type": "object", + "description": "Some connectors like Apple Pay, Airwallex and Noon might require some additional information, find specific details in the child attributes below.", + "properties": { + "apple_pay": { + "allOf": [ + { + "$ref": "#/components/schemas/ApplepayConnectorMetadataRequest" + } + ], + "nullable": true + }, + "airwallex": { + "allOf": [ + { + "$ref": "#/components/schemas/AirwallexData" + } + ], + "nullable": true + }, + "noon": { + "allOf": [ + { + "$ref": "#/components/schemas/NoonData" + } + ], + "nullable": true + } } }, "ConnectorSelection": { @@ -8883,6 +9495,11 @@ "type": "object", "description": "This field contains the Samsung Pay certificates and credentials", "nullable": true + }, + "paze": { + "type": "object", + "description": "This field contains the Paze certificates and credentials", + "nullable": true } }, "additionalProperties": false @@ -9249,11 +9866,37 @@ } ] }, + "CtpServiceDetails": { + "type": "object", + "properties": { + "merchant_transaction_id": { + "type": "string", + "description": "merchant transaction id", + "nullable": true + }, + "correlation_id": { + "type": "string", + "description": "network transaction correlation id", + "nullable": true + }, + "x_src_flow_id": { + "type": "string", + "description": "session transaction flow id", + "nullable": true + }, + "provider": { + "type": "string", + "description": "provider Eg: Visa, Mastercard", + "nullable": true + } + } + }, "Currency": { "type": "string", "description": "The three letter ISO currency code in uppercase. Eg: 'USD' for the United States Dollar.", "enum": [ "AED", + "AFN", "ALL", "AMD", "ANG", @@ -9273,10 +9916,12 @@ "BOB", "BRL", "BSD", + "BTN", "BWP", "BYN", "BZD", "CAD", + "CDF", "CHF", "CLP", "CNY", @@ -9290,6 +9935,7 @@ "DOP", "DZD", "EGP", + "ERN", "ETB", "EUR", "FJD", @@ -9311,6 +9957,8 @@ "ILS", "INR", "IQD", + "IRR", + "ISK", "JMD", "JOD", "JPY", @@ -9318,6 +9966,7 @@ "KGS", "KHR", "KMF", + "KPW", "KRW", "KWD", "KYD", @@ -9364,6 +10013,7 @@ "SAR", "SBD", "SCR", + "SDG", "SEK", "SGD", "SHP", @@ -9374,8 +10024,11 @@ "SSP", "STN", "SVC", + "SYP", "SZL", "THB", + "TJS", + "TMT", "TND", "TOP", "TRY", @@ -9397,7 +10050,8 @@ "XPF", "YER", "ZAR", - "ZMW" + "ZMW", + "ZWL" ] }, "CurrentBlockThreshold": { @@ -9688,7 +10342,7 @@ "created": { "type": "string", "format": "date-time", - "description": "A timestamp (ISO 8601 code) that determines when the customer was created", + "description": "A timestamp (ISO 8601 code) that determines when the payment method was created", "example": "2023-01-18T11:04:09.922Z", "nullable": true }, @@ -9902,6 +10556,60 @@ } } }, + "CustomerUpdateRequest": { + "type": "object", + "description": "The identifier for the customer object. If not provided the customer ID will be autogenerated.", + "properties": { + "name": { + "type": "string", + "description": "The customer's name", + "example": "Jon Test", + "nullable": true, + "maxLength": 255 + }, + "email": { + "type": "string", + "description": "The customer's email address", + "example": "JonTest@test.com", + "nullable": true, + "maxLength": 255 + }, + "phone": { + "type": "string", + "description": "The customer's phone number", + "example": "9123456789", + "nullable": true, + "maxLength": 255 + }, + "description": { + "type": "string", + "description": "An arbitrary string that you can attach to a customer object.", + "example": "First Customer", + "nullable": true, + "maxLength": 255 + }, + "phone_country_code": { + "type": "string", + "description": "The country code for the customer phone number", + "example": "+65", + "nullable": true, + "maxLength": 255 + }, + "address": { + "allOf": [ + { + "$ref": "#/components/schemas/AddressDetails" + } + ], + "nullable": true + }, + "metadata": { + "type": "object", + "description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500\ncharacters long. Metadata is useful for storing additional, structured information on an\nobject.", + "nullable": true + } + } + }, "DecoupledAuthenticationType": { "type": "string", "enum": [ @@ -9990,8 +10698,7 @@ "description": "The dispute amount" }, "currency": { - "type": "string", - "description": "The three-letter ISO currency code" + "$ref": "#/components/schemas/Currency" }, "dispute_stage": { "$ref": "#/components/schemas/DisputeStage" @@ -10186,6 +10893,81 @@ } } }, + "DynamicRoutingConfigParams": { + "type": "string", + "enum": [ + "PaymentMethod", + "PaymentMethodType", + "AuthenticationType", + "Currency", + "Country", + "CardNetwork", + "CardBin" + ] + }, + "DynamicRoutingFeatures": { + "type": "string", + "enum": [ + "metrics", + "dynamic_connector_selection", + "none" + ] + }, + "ElementPosition": { + "type": "string", + "enum": [ + "left", + "top left", + "top", + "top right", + "right", + "bottom right", + "bottom", + "bottom left", + "center" + ] + }, + "ElementSize": { + "oneOf": [ + { + "type": "object", + "required": [ + "Variants" + ], + "properties": { + "Variants": { + "$ref": "#/components/schemas/SizeVariants" + } + } + }, + { + "type": "object", + "required": [ + "Percentage" + ], + "properties": { + "Percentage": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + } + }, + { + "type": "object", + "required": [ + "Pixels" + ], + "properties": { + "Pixels": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + } + } + ] + }, "EnabledPaymentMethod": { "type": "object", "description": "Object for EnabledPaymentMethod", @@ -10240,6 +11022,16 @@ } } }, + "ErrorCategory": { + "type": "string", + "enum": [ + "frm_decline", + "processor_downtime", + "processor_decline_unauthorized", + "issue_with_payment_method", + "processor_decline_incorrect_data" + ] + }, "EventClass": { "type": "string", "enum": [ @@ -10516,6 +11308,38 @@ } } }, + "FeatureMatrixListResponse": { + "type": "object", + "required": [ + "connector_count", + "connectors" + ], + "properties": { + "connector_count": { + "type": "integer", + "description": "The number of connectors included in the response", + "minimum": 0 + }, + "connectors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ConnectorFeatureMatrixResponse" + } + } + } + }, + "FeatureMatrixRequest": { + "type": "object", + "properties": { + "connectors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Connector" + }, + "nullable": true + } + } + }, "FeatureMetadata": { "type": "object", "description": "additional data that might be required by hyperswitch", @@ -10535,9 +11359,25 @@ }, "description": "Additional tags to be used for global search", "nullable": true + }, + "apple_pay_recurring_details": { + "allOf": [ + { + "$ref": "#/components/schemas/ApplePayRecurringDetails" + } + ], + "nullable": true } } }, + "FeatureStatus": { + "type": "string", + "description": "The status of the feature", + "enum": [ + "not_supported", + "supported" + ] + }, "FieldType": { "oneOf": [ { @@ -10861,13 +11701,37 @@ { "type": "string", "enum": [ - "browser_language" + "user_bsb_number" + ] + }, + { + "type": "string", + "enum": [ + "user_bank_sort_code" ] }, { "type": "string", "enum": [ - "browser_ip" + "user_bank_routing_number" + ] + }, + { + "type": "string", + "enum": [ + "user_msisdn" + ] + }, + { + "type": "string", + "enum": [ + "user_client_identifier" + ] + }, + { + "type": "string", + "enum": [ + "order_details_product_name" ] } ], @@ -11577,6 +12441,14 @@ "type": "string", "description": "error message unified across the connectors", "nullable": true + }, + "error_category": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorCategory" + } + ], + "nullable": true } } }, @@ -11710,6 +12582,14 @@ "type": "string", "description": "error message unified across the connectors", "nullable": true + }, + "error_category": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorCategory" + } + ], + "nullable": true } } }, @@ -11806,6 +12686,14 @@ "type": "string", "description": "error message unified across the connectors", "nullable": true + }, + "error_category": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorCategory" + } + ], + "nullable": true } } }, @@ -13104,11 +13992,6 @@ ], "nullable": true }, - "metadata": { - "type": "object", - "description": "Metadata is useful for storing additional, unstructured information on an object.", - "nullable": true - }, "test_mode": { "type": "boolean", "description": "A boolean value to indicate if the connector is in Test mode. By default, its value is false.", @@ -13166,22 +14049,6 @@ }, "status": { "$ref": "#/components/schemas/ConnectorStatus" - }, - "additional_merchant_data": { - "allOf": [ - { - "$ref": "#/components/schemas/AdditionalMerchantData" - } - ], - "nullable": true - }, - "connector_wallets_details": { - "allOf": [ - { - "$ref": "#/components/schemas/ConnectorWalletDetails" - } - ], - "nullable": true } }, "additionalProperties": false @@ -13701,6 +14568,71 @@ "MobilePayRedirection": { "type": "object" }, + "MobilePaymentConsent": { + "type": "string", + "enum": [ + "consent_required", + "consent_not_required", + "consent_optional" + ] + }, + "MobilePaymentData": { + "oneOf": [ + { + "type": "object", + "required": [ + "direct_carrier_billing" + ], + "properties": { + "direct_carrier_billing": { + "type": "object", + "required": [ + "msisdn" + ], + "properties": { + "msisdn": { + "type": "string", + "description": "The phone number of the user", + "example": "1234567890" + }, + "client_uid": { + "type": "string", + "description": "Unique user id", + "example": "02iacdYXGI9CnyJdoN8c7", + "nullable": true + } + } + } + } + } + ] + }, + "MobilePaymentNextStepData": { + "type": "object", + "required": [ + "consent_data_required" + ], + "properties": { + "consent_data_required": { + "$ref": "#/components/schemas/MobilePaymentConsent" + } + } + }, + "MobilePaymentResponse": { + "allOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/MobilePaymentData" + } + ], + "nullable": true + }, + { + "type": "object" + } + ] + }, "MomoRedirection": { "type": "object" }, @@ -13731,9 +14663,81 @@ } } }, + "NetworkTransactionIdAndCardDetails": { + "type": "object", + "required": [ + "card_number", + "card_exp_month", + "card_exp_year", + "card_holder_name", + "network_transaction_id" + ], + "properties": { + "card_number": { + "type": "string", + "description": "The card number", + "example": "4242424242424242" + }, + "card_exp_month": { + "type": "string", + "description": "The card's expiry month", + "example": "24" + }, + "card_exp_year": { + "type": "string", + "description": "The card's expiry year", + "example": "24" + }, + "card_holder_name": { + "type": "string", + "description": "The card holder's name", + "example": "John Test" + }, + "card_issuer": { + "type": "string", + "description": "The name of the issuer of card", + "example": "chase", + "nullable": true + }, + "card_network": { + "allOf": [ + { + "$ref": "#/components/schemas/CardNetwork" + } + ], + "nullable": true + }, + "card_type": { + "type": "string", + "example": "CREDIT", + "nullable": true + }, + "card_issuing_country": { + "type": "string", + "example": "INDIA", + "nullable": true + }, + "bank_code": { + "type": "string", + "example": "JP_AMEX", + "nullable": true + }, + "nick_name": { + "type": "string", + "description": "The card holder's nick name", + "example": "John Test", + "nullable": true + }, + "network_transaction_id": { + "type": "string", + "description": "The network transaction ID provided by the card network during a CIT (Customer Initiated Transaction),\nwhere `setup_future_usage` is set to `off_session`." + } + } + }, "NextActionCall": { "type": "string", "enum": [ + "post_session_tokens", "confirm", "sync", "complete_authorize" @@ -13929,6 +14933,25 @@ ] } } + }, + { + "type": "object", + "description": "Contains consent to collect otp for mobile payment", + "required": [ + "consent_data_required", + "type" + ], + "properties": { + "consent_data_required": { + "$ref": "#/components/schemas/MobilePaymentConsent" + }, + "type": { + "type": "string", + "enum": [ + "collect_otp" + ] + } + } } ], "discriminator": { @@ -13943,7 +14966,8 @@ "invoke_sdk_client", "trigger_api", "display_bank_transfer_information", - "display_wait_screen" + "display_wait_screen", + "collect_otp" ] }, "NoThirdPartySdkSessionResponse": { @@ -14101,70 +15125,6 @@ } } }, - "OrderDetails": { - "type": "object", - "required": [ - "product_name", - "quantity" - ], - "properties": { - "product_name": { - "type": "string", - "description": "Name of the product that is being purchased", - "example": "shirt", - "maxLength": 255 - }, - "quantity": { - "type": "integer", - "format": "int32", - "description": "The quantity of the product to be purchased", - "example": 1, - "minimum": 0 - }, - "requires_shipping": { - "type": "boolean", - "nullable": true - }, - "product_img_link": { - "type": "string", - "description": "The image URL of the product", - "nullable": true - }, - "product_id": { - "type": "string", - "description": "ID of the product that is being purchased", - "nullable": true - }, - "category": { - "type": "string", - "description": "Category of the product that is being purchased", - "nullable": true - }, - "sub_category": { - "type": "string", - "description": "Sub category of the product that is being purchased", - "nullable": true - }, - "brand": { - "type": "string", - "description": "Brand of the product that is being purchased", - "nullable": true - }, - "product_type": { - "allOf": [ - { - "$ref": "#/components/schemas/ProductType" - } - ], - "nullable": true - }, - "product_tax_code": { - "type": "string", - "description": "The tax code for the product", - "nullable": true - } - } - }, "OrderDetailsWithAmount": { "type": "object", "required": [ @@ -14191,6 +15151,18 @@ "format": "int64", "description": "the amount per quantity of product" }, + "tax_rate": { + "type": "number", + "format": "double", + "description": "tax rate applicable to the product", + "nullable": true + }, + "total_tax_amount": { + "type": "integer", + "format": "int64", + "description": "total tax amount applicable to the product", + "nullable": true + }, "requires_shipping": { "type": "boolean", "nullable": true @@ -14242,14 +15214,17 @@ ], "properties": { "organization_name": { - "type": "string" + "type": "string", + "description": "Name of the organization" }, "organization_details": { "type": "object", + "description": "Details about the organization", "nullable": true }, "metadata": { "type": "object", + "description": "Metadata is useful for storing additional, unstructured information on an object.", "nullable": true } }, @@ -14265,20 +15240,24 @@ "properties": { "organization_id": { "type": "string", + "description": "The unique identifier for the Organization", "example": "org_q98uSGAYbjEwqs0mJwnz", "maxLength": 64, "minLength": 1 }, "organization_name": { "type": "string", + "description": "Name of the Organization", "nullable": true }, "organization_details": { "type": "object", + "description": "Details about the organization", "nullable": true }, "metadata": { "type": "object", + "description": "Metadata is useful for storing additional, unstructured information on an object.", "nullable": true }, "modified_at": { @@ -14296,14 +15275,17 @@ "properties": { "organization_name": { "type": "string", + "description": "Name of the organization", "nullable": true }, "organization_details": { "type": "object", + "description": "Details about the organization", "nullable": true }, "metadata": { "type": "object", + "description": "Metadata is useful for storing additional, unstructured information on an object.", "nullable": true } }, @@ -14585,6 +15567,17 @@ } } }, + { + "type": "object", + "required": [ + "klarna_checkout" + ], + "properties": { + "klarna_checkout": { + "type": "object" + } + } + }, { "type": "object", "required": [ @@ -14718,6 +15711,13 @@ "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 }, + "order_tax_amount": { + "type": "integer", + "format": "int64", + "description": "The payment attempt tax_amount.", + "example": 6540, + "nullable": true + }, "currency": { "allOf": [ { @@ -14846,72 +15846,28 @@ } } }, - "PaymentChargeRequest": { - "type": "object", - "description": "Fee information to be charged on the payment being collected", - "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", - "example": 6540 - }, - "transfer_account_id": { - "type": "string", - "description": "Identifier for the reseller's account to send the funds to" + "PaymentChargeType": { + "oneOf": [ + { + "type": "object", + "required": [ + "Stripe" + ], + "properties": { + "Stripe": { + "$ref": "#/components/schemas/StripeChargeType" + } + } } - } + ] }, - "PaymentChargeResponse": { - "type": "object", - "description": "Fee information to be charged on the payment being collected", - "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", - "example": 6540 - }, - "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" - } - } - } + "PaymentConnectorCategory": { + "type": "string", + "description": "Connector Access Method", + "enum": [ + "payment_gateway", + "alternative_payment_method", + "bank_acquirer" ] }, "PaymentCreatePaymentLinkConfig": { @@ -14940,9 +15896,66 @@ "one_click", "link_wallet", "invoke_payment_app", - "display_wait_screen" + "display_wait_screen", + "collect_otp" ] }, + "PaymentExperienceTypes": { + "type": "object", + "required": [ + "eligible_connectors" + ], + "properties": { + "payment_experience_type": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentExperience" + } + ], + "nullable": true + }, + "eligible_connectors": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The list of eligible connectors for a given payment experience", + "example": [ + "stripe", + "adyen" + ] + } + } + }, + "PaymentLinkBackgroundImageConfig": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string", + "description": "URL of the image", + "example": "https://hyperswitch.io/favicon.ico" + }, + "position": { + "allOf": [ + { + "$ref": "#/components/schemas/ElementPosition" + } + ], + "nullable": true + }, + "size": { + "allOf": [ + { + "$ref": "#/components/schemas/ElementSize" + } + ], + "nullable": true + } + } + }, "PaymentLinkConfig": { "type": "object", "required": [ @@ -14951,7 +15964,9 @@ "seller_name", "sdk_layout", "display_sdk_only", - "enabled_saved_payment_method" + "enabled_saved_payment_method", + "hide_card_nickname_field", + "show_card_form_by_default" ], "properties": { "theme": { @@ -14978,6 +15993,14 @@ "type": "boolean", "description": "Enable saved payment method option for payment link" }, + "hide_card_nickname_field": { + "type": "boolean", + "description": "Hide card nickname field option for payment link" + }, + "show_card_form_by_default": { + "type": "boolean", + "description": "Show card form by default for payment link" + }, "allowed_domains": { "type": "array", "items": { @@ -14994,6 +16017,32 @@ }, "description": "Dynamic details related to merchant to be rendered in payment link", "nullable": true + }, + "background_image": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentLinkBackgroundImageConfig" + } + ], + "nullable": true + }, + "details_layout": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentLinkDetailsLayout" + } + ], + "nullable": true + }, + "branding_visibility": { + "type": "boolean", + "description": "Toggle for HyperSwitch branding visibility", + "nullable": true + }, + "payment_button_text": { + "type": "string", + "description": "Text for payment link's handle confirm button", + "nullable": true } } }, @@ -15042,6 +16091,20 @@ "example": true, "nullable": true }, + "hide_card_nickname_field": { + "type": "boolean", + "description": "Hide card nickname field option for payment link", + "default": false, + "example": true, + "nullable": true + }, + "show_card_form_by_default": { + "type": "boolean", + "description": "Show card form by default for payment link", + "default": true, + "example": true, + "nullable": true + }, "transaction_details": { "type": "array", "items": { @@ -15049,9 +16112,37 @@ }, "description": "Dynamic details related to merchant to be rendered in payment link", "nullable": true + }, + "background_image": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentLinkBackgroundImageConfig" + } + ], + "nullable": true + }, + "details_layout": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentLinkDetailsLayout" + } + ], + "nullable": true + }, + "payment_button_text": { + "type": "string", + "description": "Text for payment link's handle confirm button", + "nullable": true } } }, + "PaymentLinkDetailsLayout": { + "type": "string", + "enum": [ + "layout1", + "layout2" + ] + }, "PaymentLinkInitiateRequest": { "type": "object", "required": [ @@ -15232,7 +16323,8 @@ "upi", "voucher", "gift_card", - "open_banking" + "open_banking", + "mobile_payment" ] }, "PaymentMethodCollectLinkRequest": { @@ -15639,6 +16731,18 @@ "$ref": "#/components/schemas/OpenBankingData" } } + }, + { + "type": "object", + "title": "MobilePayment", + "required": [ + "mobile_payment" + ], + "properties": { + "mobile_payment": { + "$ref": "#/components/schemas/MobilePaymentData" + } + } } ] }, @@ -15845,6 +16949,17 @@ "$ref": "#/components/schemas/OpenBankingResponse" } } + }, + { + "type": "object", + "required": [ + "mobile_payment" + ], + "properties": { + "mobile_payment": { + "$ref": "#/components/schemas/MobilePaymentResponse" + } + } } ] }, @@ -15907,28 +17022,6 @@ "jp_bacs" ] }, - "PaymentMethodList": { - "type": "object", - "required": [ - "payment_method" - ], - "properties": { - "payment_method": { - "$ref": "#/components/schemas/PaymentMethod" - }, - "payment_method_types": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PaymentMethodType" - }, - "description": "This is a sub-category of payment method.", - "example": [ - "credit" - ], - "nullable": true - } - } - }, "PaymentMethodListResponse": { "type": "object", "required": [ @@ -15952,19 +17045,9 @@ "payment_methods": { "type": "array", "items": { - "$ref": "#/components/schemas/PaymentMethodList" + "$ref": "#/components/schemas/ResponsePaymentMethodsEnabled" }, - "description": "Information about the payment method", - "example": [ - { - "payment_experience": null, - "payment_method": "wallet", - "payment_method_issuers": [ - "labore magna ipsum", - "aute" - ] - } - ] + "description": "Information about the payment method" }, "mandate_payment": { "$ref": "#/components/schemas/MandateType" @@ -16082,7 +17165,7 @@ "created": { "type": "string", "format": "date-time", - "description": "A timestamp (ISO 8601 code) that determines when the customer was created", + "description": "A timestamp (ISO 8601 code) that determines when the payment method was created", "example": "2023-01-18T11:04:09.922Z", "nullable": true }, @@ -16184,6 +17267,7 @@ "open_banking_uk", "pay_bright", "paypal", + "paze", "pix", "pay_safe_card", "przelewy24", @@ -16213,7 +17297,8 @@ "pay_easy", "local_bank_transfer", "mifinity", - "open_banking_pis" + "open_banking_pis", + "direct_carrier_billing" ] }, "PaymentMethodUpdate": { @@ -16391,7 +17476,7 @@ "amount_to_capture": { "type": "integer", "format": "int64", - "description": "The Amount to be captured/ debited from the user's payment method.", + "description": "The Amount to be captured/ debited from the user's payment method. If not passed the full amount will be captured.", "example": 6540 }, "refund_uncaptured_amount": { @@ -16450,6 +17535,13 @@ "nullable": true, "minimum": 0 }, + "order_tax_amount": { + "type": "integer", + "format": "int64", + "description": "Total tax amount applicable to the order", + "example": 6540, + "nullable": true + }, "currency": { "allOf": [ { @@ -16777,10 +17869,10 @@ ], "nullable": true }, - "charges": { + "split_payments": { "allOf": [ { - "$ref": "#/components/schemas/PaymentChargeRequest" + "$ref": "#/components/schemas/SplitPaymentsRequest" } ], "nullable": true @@ -16796,6 +17888,22 @@ "type": "boolean", "description": "Whether to calculate tax for this payment intent", "nullable": true + }, + "psd2_sca_exemption_type": { + "allOf": [ + { + "$ref": "#/components/schemas/ScaExemptionType" + } + ], + "nullable": true + }, + "ctp_service_details": { + "allOf": [ + { + "$ref": "#/components/schemas/CtpServiceDetails" + } + ], + "nullable": true } } }, @@ -16812,6 +17920,13 @@ "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies)", "minimum": 0 }, + "order_tax_amount": { + "type": "integer", + "format": "int64", + "description": "Total tax amount applicable to the order", + "example": 6540, + "nullable": true + }, "currency": { "$ref": "#/components/schemas/Currency" }, @@ -17147,10 +18262,10 @@ ], "nullable": true }, - "charges": { + "split_payments": { "allOf": [ { - "$ref": "#/components/schemas/PaymentChargeRequest" + "$ref": "#/components/schemas/SplitPaymentsRequest" } ], "nullable": true @@ -17166,6 +18281,22 @@ "type": "boolean", "description": "Whether to calculate tax for this payment intent", "nullable": true + }, + "psd2_sca_exemption_type": { + "allOf": [ + { + "$ref": "#/components/schemas/ScaExemptionType" + } + ], + "nullable": true + }, + "ctp_service_details": { + "allOf": [ + { + "$ref": "#/components/schemas/CtpServiceDetails" + } + ], + "nullable": true } } }, @@ -17216,6 +18347,13 @@ "description": "The payment net amount. net_amount = amount + surcharge_details.surcharge_amount + surcharge_details.tax_amount + shipping_cost + order_tax_amount,\nIf no surcharge_details, shipping_cost, order_tax_amount, net_amount = amount", "example": 6540 }, + "shipping_cost": { + "type": "integer", + "format": "int64", + "description": "The shipping cost for the payment.", + "example": 6540, + "nullable": true + }, "amount_capturable": { "type": "integer", "format": "int64", @@ -17666,10 +18804,10 @@ "example": "2022-09-10T10:11:12Z", "nullable": true }, - "charges": { + "split_payments": { "allOf": [ { - "$ref": "#/components/schemas/PaymentChargeResponse" + "$ref": "#/components/schemas/SplitPaymentsResponse" } ], "nullable": true @@ -17718,6 +18856,11 @@ }, "payment_method_type": { "$ref": "#/components/schemas/PaymentMethodType" + }, + "session_id": { + "type": "string", + "description": "Session Id", + "nullable": true } } }, @@ -17850,26 +18993,82 @@ } } }, - "PaymentsRequest": { + "PaymentsPostSessionTokensRequest": { "type": "object", + "required": [ + "client_secret", + "payment_method_type", + "payment_method" + ], "properties": { - "amount": { - "type": "integer", - "format": "int64", - "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies)", - "example": 6540, - "nullable": true, - "minimum": 0 + "client_secret": { + "type": "string", + "description": "It's a token used for client side verification." }, - "currency": { - "allOf": [ - { - "$ref": "#/components/schemas/Currency" - } - ], - "nullable": true + "payment_method_type": { + "$ref": "#/components/schemas/PaymentMethodType" }, - "amount_to_capture": { + "payment_method": { + "$ref": "#/components/schemas/PaymentMethod" + } + } + }, + "PaymentsPostSessionTokensResponse": { + "type": "object", + "required": [ + "payment_id", + "status" + ], + "properties": { + "payment_id": { + "type": "string", + "description": "The identifier for the payment" + }, + "next_action": { + "allOf": [ + { + "$ref": "#/components/schemas/NextActionData" + } + ], + "nullable": true + }, + "status": { + "allOf": [ + { + "$ref": "#/components/schemas/IntentStatus" + } + ], + "default": "requires_confirmation" + } + } + }, + "PaymentsRequest": { + "type": "object", + "properties": { + "amount": { + "type": "integer", + "format": "int64", + "description": "The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies)", + "example": 6540, + "nullable": true, + "minimum": 0 + }, + "order_tax_amount": { + "type": "integer", + "format": "int64", + "description": "Total tax amount applicable to the order", + "example": 6540, + "nullable": true + }, + "currency": { + "allOf": [ + { + "$ref": "#/components/schemas/Currency" + } + ], + "nullable": true + }, + "amount_to_capture": { "type": "integer", "format": "int64", "description": "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. Also, it must be less than or equal to the original payment account.", @@ -18280,10 +19479,10 @@ ], "nullable": true }, - "charges": { + "split_payments": { "allOf": [ { - "$ref": "#/components/schemas/PaymentChargeRequest" + "$ref": "#/components/schemas/SplitPaymentsRequest" } ], "nullable": true @@ -18299,6 +19498,22 @@ "type": "boolean", "description": "Whether to calculate tax for this payment intent", "nullable": true + }, + "psd2_sca_exemption_type": { + "allOf": [ + { + "$ref": "#/components/schemas/ScaExemptionType" + } + ], + "nullable": true + }, + "ctp_service_details": { + "allOf": [ + { + "$ref": "#/components/schemas/CtpServiceDetails" + } + ], + "nullable": true } }, "additionalProperties": false @@ -18350,6 +19565,13 @@ "description": "The payment net amount. net_amount = amount + surcharge_details.surcharge_amount + surcharge_details.tax_amount + shipping_cost + order_tax_amount,\nIf no surcharge_details, shipping_cost, order_tax_amount, net_amount = amount", "example": 6540 }, + "shipping_cost": { + "type": "integer", + "format": "int64", + "description": "The shipping cost for the payment.", + "example": 6540, + "nullable": true + }, "amount_capturable": { "type": "integer", "format": "int64", @@ -18825,10 +20047,10 @@ "example": "2022-09-10T10:11:12Z", "nullable": true }, - "charges": { + "split_payments": { "allOf": [ { - "$ref": "#/components/schemas/PaymentChargeResponse" + "$ref": "#/components/schemas/SplitPaymentsResponse" } ], "nullable": true @@ -18984,6 +20206,13 @@ "nullable": true, "minimum": 0 }, + "order_tax_amount": { + "type": "integer", + "format": "int64", + "description": "Total tax amount applicable to the order", + "example": 6540, + "nullable": true + }, "currency": { "allOf": [ { @@ -19306,10 +20535,10 @@ ], "nullable": true }, - "charges": { + "split_payments": { "allOf": [ { - "$ref": "#/components/schemas/PaymentChargeRequest" + "$ref": "#/components/schemas/SplitPaymentsRequest" } ], "nullable": true @@ -19325,6 +20554,22 @@ "type": "boolean", "description": "Whether to calculate tax for this payment intent", "nullable": true + }, + "psd2_sca_exemption_type": { + "allOf": [ + { + "$ref": "#/components/schemas/ScaExemptionType" + } + ], + "nullable": true + }, + "ctp_service_details": { + "allOf": [ + { + "$ref": "#/components/schemas/CtpServiceDetails" + } + ], + "nullable": true } } }, @@ -20882,6 +22127,56 @@ } } }, + "PazeSessionTokenResponse": { + "type": "object", + "required": [ + "client_id", + "client_name", + "client_profile_id", + "transaction_currency_code", + "transaction_amount" + ], + "properties": { + "client_id": { + "type": "string", + "description": "Paze Client ID" + }, + "client_name": { + "type": "string", + "description": "Client Name to be displayed on the Paze screen" + }, + "client_profile_id": { + "type": "string", + "description": "Paze Client Profile ID" + }, + "transaction_currency_code": { + "$ref": "#/components/schemas/Currency" + }, + "transaction_amount": { + "type": "string", + "description": "The transaction amount", + "example": "38.02" + }, + "email_address": { + "type": "string", + "description": "Email Address", + "example": "johntest@test.com", + "nullable": true, + "maxLength": 255 + } + } + }, + "PazeWalletData": { + "type": "object", + "required": [ + "complete_response" + ], + "properties": { + "complete_response": { + "type": "string" + } + } + }, "PhoneDetails": { "type": "object", "properties": { @@ -21219,7 +22514,7 @@ }, "is_network_tokenization_enabled": { "type": "boolean", - "description": "Indicates if is_network_tokenization_enabled is enabled or not.\nIf set to `true` is_network_tokenization_enabled will be checked." + "description": "Indicates if network tokenization is enabled or not." }, "is_auto_retries_enabled": { "type": "boolean", @@ -21232,6 +22527,15 @@ "description": "Maximum number of auto retries allowed for a payment", "nullable": true, "minimum": 0 + }, + "is_click_to_pay_enabled": { + "type": "boolean", + "description": "Indicates if click to pay is enabled or not." + }, + "authentication_product_ids": { + "type": "object", + "description": "Product authentication ids", + "nullable": true } }, "additionalProperties": false @@ -21264,7 +22568,8 @@ "redirect_to_merchant_with_http_post", "is_tax_connector_enabled", "is_network_tokenization_enabled", - "is_auto_retries_enabled" + "is_auto_retries_enabled", + "is_click_to_pay_enabled" ], "properties": { "merchant_id": { @@ -21446,7 +22751,7 @@ }, "is_network_tokenization_enabled": { "type": "boolean", - "description": "Indicates if is_network_tokenization_enabled is enabled or not.\nIf set to `true` is_network_tokenization_enabled will be checked.", + "description": "Indicates if network tokenization is enabled or not.", "default": false, "example": false }, @@ -21461,6 +22766,17 @@ "format": "int32", "description": "Maximum number of auto retries allowed for a payment", "nullable": true + }, + "is_click_to_pay_enabled": { + "type": "boolean", + "description": "Indicates if click to pay is enabled or not.", + "default": false, + "example": false + }, + "authentication_product_ids": { + "type": "object", + "description": "Product authentication ids", + "nullable": true } } }, @@ -21637,6 +22953,24 @@ "$ref": "#/components/schemas/ProcessorPaymentToken" } } + }, + { + "type": "object", + "required": [ + "type", + "data" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "network_transaction_id_and_card_details" + ] + }, + "data": { + "$ref": "#/components/schemas/NetworkTransactionIdAndCardDetails" + } + } } ], "description": "Details required for recurring payment", @@ -21644,6 +22978,16 @@ "propertyName": "type" } }, + "RecurringPaymentIntervalUnit": { + "type": "string", + "enum": [ + "year", + "month", + "day", + "hour", + "minute" + ] + }, "RedirectResponse": { "type": "object", "properties": { @@ -21657,22 +23001,6 @@ } } }, - "RefundAggregateResponse": { - "type": "object", - "required": [ - "status_with_count" - ], - "properties": { - "status_with_count": { - "type": "object", - "description": "The list of refund status with their count", - "additionalProperties": { - "type": "integer", - "format": "int64" - } - } - } - }, "RefundListRequest": { "allOf": [ { @@ -21849,10 +23177,10 @@ ], "nullable": true }, - "charges": { + "split_refunds": { "allOf": [ { - "$ref": "#/components/schemas/ChargeRefunds" + "$ref": "#/components/schemas/SplitRefund" } ], "nullable": true @@ -21940,10 +23268,10 @@ "description": "The merchant_connector_id of the processor through which this payment went through", "nullable": true }, - "charges": { + "split_refunds": { "allOf": [ { - "$ref": "#/components/schemas/ChargeRefunds" + "$ref": "#/components/schemas/SplitRefund" } ], "nullable": true @@ -21986,6 +23314,170 @@ }, "additionalProperties": false }, + "RelayData": { + "oneOf": [ + { + "type": "object", + "required": [ + "refund" + ], + "properties": { + "refund": { + "$ref": "#/components/schemas/RelayRefundRequest" + } + } + } + ] + }, + "RelayError": { + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "string", + "description": "The error code" + }, + "message": { + "type": "string", + "description": "The error message" + } + } + }, + "RelayRefundRequest": { + "type": "object", + "required": [ + "amount", + "currency" + ], + "properties": { + "amount": { + "type": "integer", + "format": "int64", + "description": "The amount that is being refunded", + "example": 6540 + }, + "currency": { + "$ref": "#/components/schemas/Currency" + }, + "reason": { + "type": "string", + "description": "The reason for the refund", + "example": "Customer returned the product", + "nullable": true, + "maxLength": 255 + } + } + }, + "RelayRequest": { + "type": "object", + "required": [ + "connector_resource_id", + "connector_id", + "type" + ], + "properties": { + "connector_resource_id": { + "type": "string", + "description": "The identifier that is associated to a resource at the connector reference to which the relay request is being made", + "example": "7256228702616471803954" + }, + "connector_id": { + "type": "string", + "description": "Identifier of the connector ( merchant connector account ) which was chosen to make the payment", + "example": "mca_5apGeP94tMts6rg3U3kR" + }, + "type": { + "$ref": "#/components/schemas/RelayType" + }, + "data": { + "allOf": [ + { + "$ref": "#/components/schemas/RelayData" + } + ], + "nullable": true + } + } + }, + "RelayResponse": { + "type": "object", + "required": [ + "id", + "status", + "connector_resource_id", + "connector_id", + "profile_id", + "type" + ], + "properties": { + "id": { + "type": "string", + "description": "The unique identifier for the Relay", + "example": "relay_mbabizu24mvu3mela5njyhpit4" + }, + "status": { + "$ref": "#/components/schemas/RelayStatus" + }, + "connector_resource_id": { + "type": "string", + "description": "The identifier that is associated to a resource at the connector reference to which the relay request is being made", + "example": "pi_3MKEivSFNglxLpam0ZaL98q9" + }, + "error": { + "allOf": [ + { + "$ref": "#/components/schemas/RelayError" + } + ], + "nullable": true + }, + "connector_reference_id": { + "type": "string", + "description": "The identifier that is associated to a resource at the connector to which the relay request is being made", + "example": "re_3QY4TnEOqOywnAIx1Mm1p7GQ", + "nullable": true + }, + "connector_id": { + "type": "string", + "description": "Identifier of the connector ( merchant connector account ) which was chosen to make the payment", + "example": "mca_5apGeP94tMts6rg3U3kR" + }, + "profile_id": { + "type": "string", + "description": "The business profile that is associated with this relay request.", + "example": "pro_abcdefghijklmnopqrstuvwxyz" + }, + "type": { + "$ref": "#/components/schemas/RelayType" + }, + "data": { + "allOf": [ + { + "$ref": "#/components/schemas/RelayData" + } + ], + "nullable": true + } + } + }, + "RelayStatus": { + "type": "string", + "enum": [ + "created", + "pending", + "success", + "failure" + ] + }, + "RelayType": { + "type": "string", + "enum": [ + "refund" + ] + }, "RequestPaymentMethodTypes": { "type": "object", "required": [ @@ -22106,6 +23598,97 @@ } } }, + "ResponsePaymentMethodTypes": { + "type": "object", + "required": [ + "payment_method_type" + ], + "properties": { + "payment_method_type": { + "$ref": "#/components/schemas/PaymentMethodType" + }, + "payment_experience": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PaymentExperienceTypes" + }, + "description": "The list of payment experiences enabled, if applicable for a payment method type", + "nullable": true + }, + "card_networks": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CardNetworkTypes" + }, + "description": "The list of card networks enabled, if applicable for a payment method type", + "nullable": true + }, + "bank_names": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BankCodeResponse" + }, + "description": "The list of banks enabled, if applicable for a payment method type", + "nullable": true + }, + "bank_debits": { + "allOf": [ + { + "$ref": "#/components/schemas/BankDebitTypes" + } + ], + "nullable": true + }, + "bank_transfers": { + "allOf": [ + { + "$ref": "#/components/schemas/BankTransferTypes" + } + ], + "nullable": true + }, + "required_fields": { + "type": "object", + "description": "Required fields for the payment_method_type.", + "additionalProperties": { + "$ref": "#/components/schemas/RequiredFieldInfo" + }, + "nullable": true + }, + "surcharge_details": { + "allOf": [ + { + "$ref": "#/components/schemas/SurchargeDetailsResponse" + } + ], + "nullable": true + }, + "pm_auth_connector": { + "type": "string", + "description": "auth service connector label for this payment method type, if exists", + "nullable": true + } + } + }, + "ResponsePaymentMethodsEnabled": { + "type": "object", + "required": [ + "payment_method", + "payment_method_types" + ], + "properties": { + "payment_method": { + "$ref": "#/components/schemas/PaymentMethod" + }, + "payment_method_types": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ResponsePaymentMethodTypes" + }, + "description": "The list of payment method types enabled for a connector account" + } + } + }, "RetrieveApiKeyResponse": { "type": "object", "description": "The response body for retrieving an API Key.", @@ -22334,8 +23917,10 @@ "cybersource", "datatrans", "deutschebank", + "digitalvirgo", "dlocal", "ebanx", + "elavon", "fiserv", "fiservemea", "fiuu", @@ -22346,11 +23931,13 @@ "helcim", "iatapay", "itaubank", + "jpmorgan", "klarna", "mifinity", "mollie", "multisafepay", "nexinets", + "nexixpay", "nmi", "noon", "novalnet", @@ -22379,6 +23966,7 @@ "wise", "worldline", "worldpay", + "xendit", "zen", "plaid", "zsl" @@ -22650,11 +24238,65 @@ "FORMAT_TOTAL_ESTIMATED_AMOUNT" ] }, + "SamsungPayAppWalletData": { + "type": "object", + "required": [ + "3_d_s", + "payment_card_brand", + "payment_currency_type", + "payment_last4_fpan" + ], + "properties": { + "3_d_s": { + "$ref": "#/components/schemas/SamsungPayTokenData" + }, + "payment_card_brand": { + "$ref": "#/components/schemas/SamsungPayCardBrand" + }, + "payment_currency_type": { + "type": "string", + "description": "Currency type of the payment" + }, + "payment_last4_dpan": { + "type": "string", + "description": "Last 4 digits of the device specific card number", + "nullable": true + }, + "payment_last4_fpan": { + "type": "string", + "description": "Last 4 digits of the card number" + }, + "merchant_ref": { + "type": "string", + "description": "Merchant reference id that was passed in the session call request", + "nullable": true + }, + "method": { + "type": "string", + "description": "Specifies authentication method used", + "nullable": true + }, + "recurring_payment": { + "type": "boolean", + "description": "Value if credential is enabled for recurring payment", + "nullable": true + } + } + }, + "SamsungPayCardBrand": { + "type": "string", + "enum": [ + "visa", + "mastercard", + "amex", + "discover", + "unknown" + ] + }, "SamsungPayMerchantPaymentInformation": { "type": "object", "required": [ "name", - "url", "country_code" ], "properties": { @@ -22664,7 +24306,8 @@ }, "url": { "type": "string", - "description": "Merchant domain that process payments" + "description": "Merchant domain that process payments, required for web payments", + "nullable": true }, "country_code": { "$ref": "#/components/schemas/CountryAlpha2" @@ -22741,7 +24384,28 @@ } } }, - "SamsungPayWalletCredentials": { + "SamsungPayWalletCredentials": { + "oneOf": [ + { + "$ref": "#/components/schemas/SamsungPayWebWalletData" + }, + { + "$ref": "#/components/schemas/SamsungPayAppWalletData" + } + ] + }, + "SamsungPayWalletData": { + "type": "object", + "required": [ + "payment_credential" + ], + "properties": { + "payment_credential": { + "$ref": "#/components/schemas/SamsungPayWalletCredentials" + } + } + }, + "SamsungPayWebWalletData": { "type": "object", "required": [ "card_brand", @@ -22760,8 +24424,7 @@ "nullable": true }, "card_brand": { - "type": "string", - "description": "Brand of the payment card" + "$ref": "#/components/schemas/SamsungPayCardBrand" }, "card_last4digits": { "type": "string", @@ -22772,16 +24435,13 @@ } } }, - "SamsungPayWalletData": { - "type": "object", - "required": [ - "payment_credential" - ], - "properties": { - "payment_credential": { - "$ref": "#/components/schemas/SamsungPayWalletCredentials" - } - } + "ScaExemptionType": { + "type": "string", + "description": "SCA Exemptions types available for authentication", + "enum": [ + "low_value", + "transaction_risk_analysis" + ] }, "SdkInformation": { "type": "object", @@ -22845,6 +24505,10 @@ "properties": { "next_action": { "$ref": "#/components/schemas/NextActionCall" + }, + "order_id": { + "type": "string", + "nullable": true } } }, @@ -22984,7 +24648,8 @@ "account_holder_name", "bic", "country", - "iban" + "iban", + "reference" ], "properties": { "account_holder_name": { @@ -23001,6 +24666,10 @@ "iban": { "type": "string", "example": "123456789" + }, + "reference": { + "type": "string", + "example": "U2PVVSEV4V9Y" } } }, @@ -23132,6 +24801,48 @@ } ] }, + { + "allOf": [ + { + "$ref": "#/components/schemas/PazeSessionTokenResponse" + }, + { + "type": "object", + "required": [ + "wallet_name" + ], + "properties": { + "wallet_name": { + "type": "string", + "enum": [ + "paze" + ] + } + } + } + ] + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/ClickToPaySessionResponse" + }, + { + "type": "object", + "required": [ + "wallet_name" + ], + "properties": { + "wallet_name": { + "type": "string", + "enum": [ + "click_to_pay" + ] + } + } + } + ] + }, { "type": "object", "required": [ @@ -23202,6 +24913,60 @@ } ] }, + "SizeVariants": { + "type": "string", + "enum": [ + "cover", + "contain" + ] + }, + "SplitPaymentsRequest": { + "oneOf": [ + { + "type": "object", + "required": [ + "stripe_split_payment" + ], + "properties": { + "stripe_split_payment": { + "$ref": "#/components/schemas/StripeSplitPaymentRequest" + } + } + } + ], + "description": "Fee information for Split Payments to be charged on the payment being collected" + }, + "SplitPaymentsResponse": { + "oneOf": [ + { + "type": "object", + "required": [ + "stripe_split_payment" + ], + "properties": { + "stripe_split_payment": { + "$ref": "#/components/schemas/StripeSplitPaymentsResponse" + } + } + } + ] + }, + "SplitRefund": { + "oneOf": [ + { + "type": "object", + "required": [ + "stripe_split_refund" + ], + "properties": { + "stripe_split_refund": { + "$ref": "#/components/schemas/StripeSplitRefundRequest" + } + } + } + ], + "description": "Charge specific fields for controlling the revert of funds from either platform or connected account. Check sub-fields for more details." + }, "StraightThroughAlgorithm": { "oneOf": [ { @@ -23279,13 +25044,84 @@ "destination" ] }, + "StripeSplitPaymentRequest": { + "type": "object", + "description": "Fee information for Split Payments to be charged on the payment being collected for Stripe", + "required": [ + "charge_type", + "application_fees", + "transfer_account_id" + ], + "properties": { + "charge_type": { + "$ref": "#/components/schemas/PaymentChargeType" + }, + "application_fees": { + "type": "integer", + "format": "int64", + "description": "Platform fees to be collected on the payment", + "example": 6540 + }, + "transfer_account_id": { + "type": "string", + "description": "Identifier for the reseller's account to send the funds to" + } + }, + "additionalProperties": false + }, + "StripeSplitPaymentsResponse": { + "type": "object", + "description": "Fee information to be charged on the payment being collected", + "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", + "example": 6540 + }, + "transfer_account_id": { + "type": "string", + "description": "Identifier for the reseller's account where the funds were transferred" + } + } + }, + "StripeSplitRefundRequest": { + "type": "object", + "description": "Charge specific fields for controlling the revert of funds from either platform or connected account for Stripe. Check sub-fields for more details.", + "properties": { + "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 + } + }, + "additionalProperties": false + }, "SuccessBasedRoutingConfig": { "type": "object", "properties": { "params": { "type": "array", "items": { - "$ref": "#/components/schemas/SuccessBasedRoutingConfigParams" + "$ref": "#/components/schemas/DynamicRoutingConfigParams" }, "nullable": true }, @@ -23329,27 +25165,49 @@ } } }, - "SuccessBasedRoutingConfigParams": { - "type": "string", - "enum": [ - "PaymentMethod", - "PaymentMethodType", - "Currency", - "AuthenticationType" - ] - }, - "SuccessBasedRoutingUpdateConfigQuery": { + "SupportedPaymentMethod": { "type": "object", "required": [ - "algorithm_id", - "profile_id" + "payment_method", + "payment_method_type", + "mandates", + "refunds", + "supported_capture_methods" ], "properties": { - "algorithm_id": { - "type": "string" + "payment_method": { + "$ref": "#/components/schemas/PaymentMethod" }, - "profile_id": { - "type": "string" + "payment_method_type": { + "$ref": "#/components/schemas/PaymentMethodType" + }, + "mandates": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "refunds": { + "$ref": "#/components/schemas/FeatureStatus" + }, + "supported_capture_methods": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CaptureMethod" + } + }, + "supported_countries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CountryAlpha2" + }, + "uniqueItems": true, + "nullable": true + }, + "supported_currencies": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Currency" + }, + "uniqueItems": true, + "nullable": true } } }, @@ -23359,8 +25217,7 @@ "surcharge", "display_surcharge_amount", "display_tax_on_surcharge_amount", - "display_total_surcharge_amount", - "display_final_amount" + "display_total_surcharge_amount" ], "properties": { "surcharge": { @@ -23388,11 +25245,6 @@ "type": "number", "format": "double", "description": "sum of display_surcharge_amount and display_tax_on_surcharge_amount" - }, - "display_final_amount": { - "type": "number", - "format": "double", - "description": "sum of original amount," } } }, @@ -23575,6 +25427,28 @@ } } }, + "ToggleDynamicRoutingPath": { + "type": "object", + "required": [ + "profile_id" + ], + "properties": { + "profile_id": { + "type": "string" + } + } + }, + "ToggleDynamicRoutingQuery": { + "type": "object", + "required": [ + "enable" + ], + "properties": { + "enable": { + "$ref": "#/components/schemas/DynamicRoutingFeatures" + } + } + }, "ToggleKVRequest": { "type": "object", "required": [ @@ -23608,28 +25482,6 @@ } } }, - "ToggleSuccessBasedRoutingPath": { - "type": "object", - "required": [ - "profile_id" - ], - "properties": { - "profile_id": { - "type": "string" - } - } - }, - "ToggleSuccessBasedRoutingQuery": { - "type": "object", - "required": [ - "status" - ], - "properties": { - "status": { - "type": "boolean" - } - } - }, "TouchNGoRedirection": { "type": "object" }, @@ -24388,6 +26240,17 @@ } } }, + { + "type": "object", + "required": [ + "paze" + ], + "properties": { + "paze": { + "$ref": "#/components/schemas/PazeWalletData" + } + } + }, { "type": "object", "required": [ @@ -24530,7 +26393,8 @@ } } } - ] + ], + "description": "Hyperswitch supports SDK integration with Apple Pay and Google Pay wallets. For other wallets, we integrate with their respective connectors, redirecting the customer to the connector for wallet payments. As a result, we don’t receive any payment method data in the confirm call for payments made through other wallets." }, "WeChatPay": { "type": "object" diff --git a/api-reference/rust_locker_open_api_spec.yml b/api-reference/rust_locker_open_api_spec.yml index 729886d9cd0d..17a19fec44da 100644 --- a/api-reference/rust_locker_open_api_spec.yml +++ b/api-reference/rust_locker_open_api_spec.yml @@ -2,16 +2,16 @@ openapi: "3.0.2" info: title: Tartarus - OpenAPI 3.0 description: |- - This the the open API 3.0 specification for the card locker. + This is the OpenAPI 3.0 specification for the card locker. This is used by the [hyperswitch](https://github.com/juspay/hyperswitch) for storing card information securely. version: "1.0" tags: - name: Key Custodian description: API used to initialize the locker after deployment. - name: Data - description: CRUD APIs to for working with data to be stored in the locker + description: CRUD APIs for working with data to be stored in the locker - name: Cards - description: CRUD APIs to for working with cards data to be stored in the locker (deprecated) + description: CRUD APIs for working with cards data to be stored in the locker (deprecated) paths: /custodian/key1: post: @@ -39,7 +39,7 @@ paths: tags: - Key Custodian summary: Provide Key 2 - description: Provide the first key to unlock the locker + description: Provide the second key to unlock the locker operationId: setKey2 requestBody: description: Provide key 2 to unlock the locker diff --git a/aws/hyperswitch_aws_setup.sh b/aws/hyperswitch_aws_setup.sh index dd71b698e93e..e3af286a58da 100644 --- a/aws/hyperswitch_aws_setup.sh +++ b/aws/hyperswitch_aws_setup.sh @@ -38,12 +38,11 @@ echo "Creating Security Group for Application..." export EC2_SG="application-sg" -echo `(aws ec2 create-security-group \ +echo "$(aws ec2 create-security-group \ --region $REGION \ --group-name $EC2_SG \ --description "Security Group for Hyperswitch EC2 instance" \ ---tag-specifications "ResourceType=security-group,Tags=[{Key=ManagedBy,Value=hyperswitch}]" \ -)` +--tag-specifications "ResourceType=security-group,Tags=[{Key=ManagedBy,Value=hyperswitch}]")" export APP_SG_ID=$(aws ec2 describe-security-groups --group-names $EC2_SG --region $REGION --output text --query 'SecurityGroups[0].GroupId') @@ -51,24 +50,23 @@ echo "Security Group for Application CREATED.\n" echo "Creating Security Group ingress for port 80..." -echo `aws ec2 authorize-security-group-ingress \ +echo "$(aws ec2 authorize-security-group-ingress \ --group-id $APP_SG_ID \ --protocol tcp \ --port 80 \ --cidr 0.0.0.0/0 \ ---region $REGION` +--region $REGION)" echo "Security Group ingress for port 80 CREATED.\n" - echo "Creating Security Group ingress for port 22..." -echo `aws ec2 authorize-security-group-ingress \ +echo "$(aws ec2 authorize-security-group-ingress \ --group-id $APP_SG_ID \ --protocol tcp \ --port 22 \ --cidr 0.0.0.0/0 \ ---region $REGION` +--region $REGION)" echo "Security Group ingress for port 22 CREATED.\n" @@ -78,11 +76,11 @@ echo "Security Group ingress for port 22 CREATED.\n" echo "Creating Security Group for Elasticache..." export REDIS_GROUP_NAME=redis-sg -echo `aws ec2 create-security-group \ +echo "$(aws ec2 create-security-group \ --group-name $REDIS_GROUP_NAME \ --description "SG attached to elasticache" \ --tag-specifications "ResourceType=security-group,Tags=[{Key=ManagedBy,Value=hyperswitch}]" \ ---region $REGION` +--region $REGION)" echo "Security Group for Elasticache CREATED.\n" @@ -91,12 +89,12 @@ echo "Creating Inbound rules for Redis..." export REDIS_SG_ID=$(aws ec2 describe-security-groups --group-names $REDIS_GROUP_NAME --region $REGION --output text --query 'SecurityGroups[0].GroupId') # CREATE INBOUND RULES -echo `aws ec2 authorize-security-group-ingress \ +echo "$(aws ec2 authorize-security-group-ingress \ --group-id $REDIS_SG_ID \ --protocol tcp \ --port 6379 \ --source-group $EC2_SG \ ---region $REGION` +--region $REGION)" echo "Inbound rules for Redis CREATED.\n" @@ -105,11 +103,11 @@ echo "Inbound rules for Redis CREATED.\n" echo "Creating Security Group for RDS..." export RDS_GROUP_NAME=rds-sg -echo `aws ec2 create-security-group \ +echo "$(aws ec2 create-security-group \ --group-name $RDS_GROUP_NAME \ --description "SG attached to RDS" \ --tag-specifications "ResourceType=security-group,Tags=[{Key=ManagedBy,Value=hyperswitch}]" \ ---region $REGION` +--region $REGION)" echo "Security Group for RDS CREATED.\n" @@ -118,21 +116,21 @@ echo "Creating Inbound rules for RDS..." export RDS_SG_ID=$(aws ec2 describe-security-groups --group-names $RDS_GROUP_NAME --region $REGION --output text --query 'SecurityGroups[0].GroupId') # CREATE INBOUND RULES -echo `aws ec2 authorize-security-group-ingress \ +echo "$(aws ec2 authorize-security-group-ingress \ --group-id $RDS_SG_ID \ --protocol tcp \ --port 5432 \ --source-group $EC2_SG \ ---region $REGION` +--region $REGION)" echo "Inbound rules for RDS CREATED.\n" -echo `aws ec2 authorize-security-group-ingress \ +echo "$(aws ec2 authorize-security-group-ingress \ --group-id $RDS_SG_ID \ --protocol tcp \ --port 5432 \ --cidr 0.0.0.0/0 \ - --region $REGION` + --region $REGION)" echo "Inbound rules for RDS (from any IP) CREATED.\n" @@ -140,7 +138,7 @@ echo "Creating Elasticache with Redis engine..." export CACHE_CLUSTER_ID=hyperswitch-cluster -echo `aws elasticache create-cache-cluster \ +echo "$(aws elasticache create-cache-cluster \ --cache-cluster-id $CACHE_CLUSTER_ID \ --cache-node-type cache.t3.medium \ --engine redis \ @@ -148,14 +146,14 @@ echo `aws elasticache create-cache-cluster \ --security-group-ids $REDIS_SG_ID \ --engine-version 7.0 \ --tags "Key=ManagedBy,Value=hyperswitch" \ ---region $REGION` +--region $REGION)" echo "Elasticache with Redis engine CREATED.\n" echo "Creating RDS with PSQL..." export DB_INSTANCE_ID=hyperswitch-db -echo `aws rds create-db-instance \ +echo "$(aws rds create-db-instance \ --db-instance-identifier $DB_INSTANCE_ID\ --db-instance-class db.t3.micro \ --engine postgres \ @@ -166,7 +164,7 @@ echo `aws rds create-db-instance \ --region $REGION \ --db-name hyperswitch_db \ --tags "Key=ManagedBy,Value=hyperswitch" \ - --vpc-security-group-ids $RDS_SG_ID` + --vpc-security-group-ids $RDS_SG_ID)" echo "RDS with PSQL CREATED.\n" @@ -308,17 +306,17 @@ echo "EC2 instance launched.\n" echo "Add Tags to EC2 instance..." -echo `aws ec2 create-tags \ +echo "$(aws ec2 create-tags \ --resources $HYPERSWITCH_INSTANCE_ID \ --tags "Key=Name,Value=hyperswitch-router" \ ---region $REGION` +--region $REGION)" echo "Tag added to EC2 instance.\n" -echo `aws ec2 create-tags \ +echo "$(aws ec2 create-tags \ --resources $HYPERSWITCH_INSTANCE_ID \ --tags "Key=ManagedBy,Value=hyperswitch" \ ---region $REGION` +--region $REGION)" echo "ManagedBy tag added to EC2 instance.\n" diff --git a/aws/hyperswitch_cleanup_setup.sh b/aws/hyperswitch_cleanup_setup.sh index 383f23d5bd00..df75623746c9 100644 --- a/aws/hyperswitch_cleanup_setup.sh +++ b/aws/hyperswitch_cleanup_setup.sh @@ -45,7 +45,7 @@ for cluster_arn in $ALL_ELASTIC_CACHE; do if [[ $? -eq 0 ]]; then echo -n "Delete $cluster_id (Y/n)? " if yes_or_no; then - echo `aws elasticache delete-cache-cluster --region $REGION --cache-cluster-id $cluster_id` + echo "$(aws elasticache delete-cache-cluster --region $REGION --cache-cluster-id $cluster_id)" fi fi done @@ -59,7 +59,7 @@ echo -n "Deleting ( $ALL_KEY_PAIRS ) key pairs? (Y/n)?" if yes_or_no; then for KEY_ID in $ALL_KEY_PAIRS; do - echo `aws ec2 delete-key-pair --key-pair-id $KEY_ID --region $REGION` + echo "$(aws ec2 delete-key-pair --key-pair-id $KEY_ID --region $REGION)" done fi @@ -78,7 +78,7 @@ echo -n "Terminating ( $ALL_INSTANCES ) instances? (Y/n)?" if yes_or_no; then for INSTANCE_ID in $ALL_INSTANCES; do - echo `aws ec2 terminate-instances --instance-ids $INSTANCE_ID --region $REGION` + echo "$(aws ec2 terminate-instances --instance-ids $INSTANCE_ID --region $REGION)" done fi @@ -105,15 +105,15 @@ for resource_id in $ALL_DB_RESOURCES; do echo -n "Create a snapshot before deleting ( $DB_INSTANCE_ID ) the database (Y/n)? " if yes_or_no; then - echo `aws rds delete-db-instance \ + echo "$(aws rds delete-db-instance \ --db-instance-identifier $DB_INSTANCE_ID \ --region $REGION \ - --final-db-snapshot-identifier hyperswitch-db-snapshot-`date +%s`` + --final-db-snapshot-identifier hyperswitch-db-snapshot-$(date +%s))" else - echo `aws rds delete-db-instance \ + echo "$(aws rds delete-db-instance \ --region $REGION \ --db-instance-identifier $DB_INSTANCE_ID \ - --skip-final-snapshot` + --skip-final-snapshot)" fi fi fi diff --git a/config/config.example.toml b/config/config.example.toml index a6aeba7427ab..999266321144 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -183,6 +183,7 @@ adyen.payout_base_url = "https://pal-test.adyen.com/" adyen.dispute_base_url = "https://ca-test.adyen.com/" adyenplatform.base_url = "https://balanceplatform-api-test.adyen.com/" airwallex.base_url = "https://api-demo.airwallex.com/" +amazonpay.base_url = "https://pay-api.amazon.com/v2" applepay.base_url = "https://apple-pay-gateway.apple.com/" authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api" bambora.base_url = "https://api.na.bambora.com" @@ -202,9 +203,11 @@ cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" deutschebank.base_url = "https://testmerch.directpos.de/rest-api" +digitalvirgo.base_url = "https://dcb-integration-service-sandbox-external.staging.digitalvirgo.pl" dlocal.base_url = "https://sandbox.dlocal.com/" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" +elavon.base_url = "https://api.demo.convergepay.com/VirtualMerchantDemo/" fiserv.base_url = "https://cert.api.fiservapps.com/" fiservemea.base_url = "https://prod.emea.api.fiservapps.com/sandbox" fiuu.base_url = "https://sandbox.merchant.razer.com/" @@ -217,7 +220,10 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" +inespay.base_url = "https://apiflow.inespay.com/san/v21" itaubank.base_url = "https://sandbox.devportal.itau.com.br/" +jpmorgan.base_url = "https://api-mock.payments.jpmorgan.com/api/v2" +jpmorgan.secondary_base_url= "https://id.payments.jpmorgan.com" klarna.base_url = "https://api{{klarna_region}}.playground.klarna.com/" mifinity.base_url = "https://demo.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" @@ -227,6 +233,7 @@ netcetera.base_url = "https://{{merchant_endpoint_prefix}}.3ds-server.prev.netce nexinets.base_url = "https://apitest.payengine.de/v1" nexixpay.base_url = "https://xpaysandbox.nexigroup.com/api/phoenix-0.0/psp/api/v1" nmi.base_url = "https://secure.nmi.com/" +nomupay.base_url = "https://payout-api.sandbox.nomupay.com" noon.base_url = "https://api-test.noonpayments.com/" novalnet.base_url = "https://payport.novalnet.de/v2" noon.key_mode = "Test" @@ -246,6 +253,7 @@ powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" razorpay.base_url = "https://sandbox.juspay.in/" +redsys.base_url = "https://sis-t.redsys.es:25443/sis/realizarPago" riskified.base_url = "https://sandbox.riskified.com/api" shift4.base_url = "https://api.shift4.com/" signifyd.base_url = "https://api.signifyd.com/" @@ -260,12 +268,14 @@ stripe.base_url_file_upload = "https://files.stripe.com/" trustpay.base_url = "https://test-tpgw.trustpay.eu/" trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/" tsys.base_url = "https://stagegw.transnox.com/" +unified_authentication_service.base_url = "http://localhost:8000" volt.base_url = "https://api.sandbox.volt.io/" wellsfargo.base_url = "https://apitest.cybersource.com/" wellsfargopayout.base_url = "https://api-sandbox.wellsfargo.com/" wise.base_url = "https://api.sandbox.transferwise.tech/" worldline.base_url = "https://eu.sandbox.api-ingenico.com/" worldpay.base_url = "https://try.access.worldpay.com/" +xendit.base_url = "https://api.xendit.co" zsl.base_url = "https://api.sitoffalb.net/" zen.base_url = "https://api.zen-test.com/" zen.secondary_base_url = "https://secure.zen-test.com/" @@ -309,6 +319,7 @@ cards = [ "cybersource", "datatrans", "deutschebank", + "digitalvirgo", "globalpay", "globepay", "gocardless", @@ -323,6 +334,7 @@ cards = [ "threedsecureio", "thunes", "worldpay", + "xendit", "zen", "zsl", ] @@ -393,6 +405,8 @@ password_validity_in_days = 90 # Number of days after which password shoul two_factor_auth_expiry_in_secs = 300 # Number of seconds after which 2FA should be done again if doing update/change from inside totp_issuer_name = "Hyperswitch" # Name of the issuer for TOTP base_url = "" # Base url used for user specific redirects and emails +force_two_factor_auth = false # Whether to force two factor authentication for all users +force_cookies = true # Whether to use only cookies for JWT extraction and authentication #tokenization configuration which describe token lifetime and payment method for specific connector [tokenization] @@ -416,6 +430,7 @@ nmi = { payment_method = "card" } payme = { payment_method = "card" } deutschebank = { payment_method = "bank_debit" } paybox = { payment_method = "card" } +nexixpay = { payment_method = "card" } [dummy_connector] enabled = true # Whether dummy connector is enabled or not @@ -501,6 +516,10 @@ seicomart = { country = "JP", currency = "JPY" } pay_easy = { country = "JP", currency = "JPY" } boleto = { country = "BR", currency = "BRL" } +[pm_filters.bambora] +credit = { country = "US,CA", currency = "USD" } +debit = { country = "US,CA", currency = "USD" } + [pm_filters.volt] open_banking_uk = { country = "DE,GB,AT,BE,CY,EE,ES,FI,FR,GR,HR,IE,IT,LT,LU,LV,MT,NL,PT,SI,SK,BG,CZ,DK,HU,NO,PL,RO,SE,AU,BR", currency = "EUR,GBP,DKK,NOK,PLN,SEK,AUD,BRL" } @@ -532,11 +551,12 @@ apple_pay = { currency = "USD" } google_pay = { currency = "USD" } [pm_filters.cybersource] -credit = { currency = "USD,GBP,EUR" } -debit = { currency = "USD,GBP,EUR" } -apple_pay = { currency = "USD,GBP,EUR" } -google_pay = { currency = "USD,GBP,EUR" } +credit = { currency = "USD,GBP,EUR,PLN" } +debit = { currency = "USD,GBP,EUR,PLN" } +apple_pay = { currency = "USD,GBP,EUR,PLN" } +google_pay = { currency = "USD,GBP,EUR,PLN" } samsung_pay = { currency = "USD,GBP,EUR" } +paze = { currency = "USD" } [pm_filters.stax] credit = { currency = "USD" } @@ -553,6 +573,17 @@ debit = { currency = "USD" } [pm_filters.klarna] klarna = { country = "AU,AT,BE,CA,CZ,DK,FI,FR,DE,GR,IE,IT,NL,NZ,NO,PL,PT,ES,SE,CH,GB,US", currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" } +[pm_filters.nexixpay] +credit = { country = "AT,BE,CY,EE,FI,FR,DE,GR,IE,IT,LV,LT,LU,MT,NL,PT,SK,SI,ES,BG,HR,DK,GB,NO,PL,CZ,RO,SE,CH,HU", currency = "ARS,AUD,BHD,CAD,CLP,CNY,COP,HRK,CZK,DKK,HKD,HUF,INR,JPY,KZT,JOD,KRW,KWD,MYR,MXN,NGN,NOK,PHP,QAR,RUB,SAR,SGD,VND,ZAR,SEK,CHF,THB,AED,EGP,GBP,USD,TWD,BYN,RSD,AZN,RON,TRY,AOA,BGN,EUR,UAH,PLN,BRL" } +debit = { country = "AT,BE,CY,EE,FI,FR,DE,GR,IE,IT,LV,LT,LU,MT,NL,PT,SK,SI,ES,BG,HR,DK,GB,NO,PL,CZ,RO,SE,CH,HU", currency = "ARS,AUD,BHD,CAD,CLP,CNY,COP,HRK,CZK,DKK,HKD,HUF,INR,JPY,KZT,JOD,KRW,KWD,MYR,MXN,NGN,NOK,PHP,QAR,RUB,SAR,SGD,VND,ZAR,SEK,CHF,THB,AED,EGP,GBP,USD,TWD,BYN,RSD,AZN,RON,TRY,AOA,BGN,EUR,UAH,PLN,BRL" } + +[pm_filters.novalnet] +credit = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} +debit = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} +apple_pay = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} +google_pay = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} +paypal = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} + [pm_filters.mifinity] mifinity = { country = "BR,CN,SG,MY,DE,CH,DK,GB,ES,AD,GI,FI,FR,GR,HR,IT,JP,MX,AR,CO,CL,PE,VE,UY,PY,BO,EC,GT,HN,SV,NI,CR,PA,DO,CU,PR,NL,NO,PL,PT,SE,RU,TR,TW,HK,MO,AX,AL,DZ,AS,AO,AI,AG,AM,AW,AU,AT,AZ,BS,BH,BD,BB,BE,BZ,BJ,BM,BT,BQ,BA,BW,IO,BN,BG,BF,BI,KH,CM,CA,CV,KY,CF,TD,CX,CC,KM,CG,CK,CI,CW,CY,CZ,DJ,DM,EG,GQ,ER,EE,ET,FK,FO,FJ,GF,PF,TF,GA,GM,GE,GH,GL,GD,GP,GU,GG,GN,GW,GY,HT,HM,VA,IS,IN,ID,IE,IM,IL,JE,JO,KZ,KE,KI,KW,KG,LA,LV,LB,LS,LI,LT,LU,MK,MG,MW,MV,ML,MT,MH,MQ,MR,MU,YT,FM,MD,MC,MN,ME,MS,MA,MZ,NA,NR,NP,NC,NZ,NE,NG,NU,NF,MP,OM,PK,PW,PS,PG,PH,PN,QA,RE,RO,RW,BL,SH,KN,LC,MF,PM,VC,WS,SM,ST,SA,SN,RS,SC,SL,SX,SK,SI,SB,SO,ZA,GS,KR,LK,SR,SJ,SZ,TH,TL,TG,TK,TO,TT,TN,TM,TC,TV,UG,UA,AE,UZ,VU,VN,VG,VI,WF,EH,ZM", currency = "AUD,CAD,CHF,CNY,CZK,DKK,EUR,GBP,INR,JPY,NOK,NZD,PLN,RUB,SEK,ZAR,USD,EGP,UYU,UZS" } @@ -574,6 +605,10 @@ apple_pay_ppc_key = "APPLE_PAY_PAYMENT_PROCESSING_CERTIFICATE_KEY" # Private key apple_pay_merchant_cert = "APPLE_PAY_MERCHNAT_CERTIFICATE" # Merchant Certificate provided by Apple Pay (https://developer.apple.com/) Certificates, Identifiers & Profiles > Apple Pay Merchant Identity Certificate apple_pay_merchant_cert_key = "APPLE_PAY_MERCHNAT_CERTIFICATE_KEY" # Private key generated by RSA:2048 algorithm. Refer Hyperswitch Docs (https://docs.hyperswitch.io/hyperswitch-cloud/payment-methods-setup/wallets/apple-pay/ios-application/) to generate the private key +[paze_decrypt_keys] +paze_private_key = "PAZE_PRIVATE_KEY" # Base 64 Encoded Private Key File cakey.pem generated for Paze -> Command to create private key: openssl req -newkey rsa:2048 -x509 -keyout cakey.pem -out cacert.pem -days 365 +paze_private_key_passphrase = "PAZE_PRIVATE_KEY_PASSPHRASE" # PEM Passphrase used for generating Private Key File cakey.pem + [applepay_merchant_configs] # Run below command to get common merchant identifier for applepay in shell # @@ -631,6 +666,7 @@ pm_auth_key = "Some_pm_auth_key" # Analytics configuration. [analytics] source = "sqlx" # The Analytics source/strategy to be used +forex_enabled = false # Enable or disable forex conversion for analytics [analytics.clickhouse] username = "" # Clickhouse username @@ -716,6 +752,10 @@ payment_attempts = "hyperswitch-payment-attempt-events" payment_intents = "hyperswitch-payment-intent-events" refunds = "hyperswitch-refund-events" disputes = "hyperswitch-dispute-events" +sessionizer_payment_attempts = "sessionizer-payment-attempt-events" +sessionizer_payment_intents = "sessionizer-payment-intent-events" +sessionizer_refunds = "sessionizer-refund-events" +sessionizer_disputes = "sessionizer-dispute-events" [saved_payment_methods] sdk_eligible_payment_methods = "card" @@ -724,8 +764,15 @@ sdk_eligible_payment_methods = "card" enabled = false global_tenant = { schema = "public", redis_key_prefix = "", clickhouse_database = "default"} -[multitenancy.tenants] -public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default" } # schema -> Postgres db schema, redis_key_prefix -> redis key distinguisher, base_url -> url of the tenant +[multitenancy.tenants.public] +base_url = "http://localhost:8080" # URL of the tenant +schema = "public" # Postgres db schema +redis_key_prefix = "" # Redis key distinguisher +clickhouse_database = "default" # Clickhouse database + +[multitenancy.tenants.public.user] +control_center_url = "http://localhost:9000" # Control center URL + [user_auth_methods] encryption_key = "" # Encryption key used for encrypting data in user_authentication_methods table @@ -755,6 +802,20 @@ check_token_status_url= "" # base url to check token status from token servic [network_tokenization_supported_connectors] connector_list = "cybersource" # Supported connectors for network tokenization +[network_transaction_id_supported_connectors] +connector_list = "stripe,adyen,cybersource" # Supported connectors for network transaction id + [grpc_client.dynamic_routing_client] # Dynamic Routing Client Configuration host = "localhost" # Client Host port = 7000 # Client Port +service = "dynamo" # Service name + +[theme.storage] +file_storage_backend = "file_system" # Theme storage backend to be used + +[theme.email_config] +entity_name = "Hyperswitch" # Name of the entity to be showed in emails +entity_logo_url = "https://example.com/logo.svg" # Logo URL of the entity to be used in emails +foreground_color = "#000000" # Foreground color of email text +primary_color = "#006DF9" # Primary color of email body +background_color = "#FFFFFF" # Background color of email body diff --git a/config/dashboard.toml b/config/dashboard.toml index 4cafdd49ab3c..87c007217367 100644 --- a/config/dashboard.toml +++ b/config/dashboard.toml @@ -35,5 +35,6 @@ global_search=true dispute_analytics=true configure_pmts=false branding=false +user_management_revamp=true totp=true live_users_counter=false \ No newline at end of file diff --git a/config/deployments/env_specific.toml b/config/deployments/env_specific.toml index db43cac979b7..967b847dae51 100644 --- a/config/deployments/env_specific.toml +++ b/config/deployments/env_specific.toml @@ -9,6 +9,7 @@ database_name = "clickhouse_db_name" # Clickhouse database name # Analytics configuration. [analytics] source = "sqlx" # The Analytics source/strategy to be used +forex_enabled = false # Boolean to enable or disable forex conversion [analytics.sqlx] username = "db_user" # Analytics DB Username @@ -29,6 +30,10 @@ apple_pay_ppc_key = "APPLE_PAY_PAYMENT_PROCESSING_CERTIFICATE_KEY" # Private key apple_pay_merchant_cert = "APPLE_PAY_MERCHNAT_CERTIFICATE" # Merchant Certificate provided by Apple Pay (https://developer.apple.com/) Certificates, Identifiers & Profiles > Apple Pay Merchant Identity Certificate apple_pay_merchant_cert_key = "APPLE_PAY_MERCHNAT_CERTIFICATE_KEY" # Private key generated by RSA:2048 algorithm. Refer Hyperswitch Docs (https://docs.hyperswitch.io/hyperswitch-cloud/payment-methods-setup/wallets/apple-pay/ios-application/) to generate the private key +[paze_decrypt_keys] +paze_private_key = "PAZE_PRIVATE_KEY" # Base 64 Encoded Private Key File cakey.pem generated for Paze -> Command to create private key: openssl req -newkey rsa:2048 -x509 -keyout cakey.pem -out cacert.pem -days 365 +paze_private_key_passphrase = "PAZE_PRIVATE_KEY_PASSPHRASE" # PEM Passphrase used for generating Private Key File cakey.pem + [applepay_merchant_configs] common_merchant_identifier = "APPLE_PAY_COMMON_MERCHANT_IDENTIFIER" # Refer to config.example.toml to learn how you can generate this value merchant_cert = "APPLE_PAY_MERCHANT_CERTIFICATE" # Merchant Certificate provided by Apple Pay (https://developer.apple.com/) Certificates, Identifiers & Profiles > Apple Pay Merchant Identity Certificate @@ -248,6 +253,10 @@ payment_attempts = "hyperswitch-payment-attempt-events" payment_intents = "hyperswitch-payment-intent-events" refunds = "hyperswitch-refund-events" disputes = "hyperswitch-dispute-events" +sessionizer_payment_attempts = "sessionizer-payment-attempt-events" +sessionizer_payment_intents = "sessionizer-payment-intent-events" +sessionizer_refunds = "sessionizer-refund-events" +sessionizer_disputes = "sessionizer-dispute-events" # Configuration for the Key Manager Service [key_manager] @@ -296,8 +305,14 @@ region = "kms_region" # The AWS region used by the KMS SDK for decrypting data. enabled = false global_tenant = { schema = "public", redis_key_prefix = "", clickhouse_database = "default"} -[multitenancy.tenants] -public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default" } +[multitenancy.tenants.public] +base_url = "http://localhost:8080" +schema = "public" +redis_key_prefix = "" +clickhouse_database = "default" + +[multitenancy.tenants.public.user] +control_center_url = "http://localhost:9000" [user_auth_methods] encryption_key = "user_auth_table_encryption_key" # Encryption key used for encrypting data in user_authentication_methods table @@ -318,3 +333,21 @@ check_token_status_url= "" # base url to check token status from token servic [grpc_client.dynamic_routing_client] # Dynamic Routing Client Configuration host = "localhost" # Client Host port = 7000 # Client Port +service = "dynamo" # Service name + +[theme.storage] +file_storage_backend = "aws_s3" # Theme storage backend to be used + +[theme.storage.aws_s3] +region = "bucket_region" # AWS region where the S3 bucket for theme storage is located +bucket_name = "bucket" # AWS S3 bucket name for theme storage + +[theme.email_config] +entity_name = "Hyperswitch" # Name of the entity to be showed in emails +entity_logo_url = "https://example.com/logo.svg" # Logo URL of the entity to be used in emails +foreground_color = "#000000" # Foreground color of email text +primary_color = "#006DF9" # Primary color of email body +background_color = "#FFFFFF" # Background color of email body + +[connectors.unified_authentication_service] #Unified Authentication Service Configuration +base_url = "http://localhost:8000" #base url to call unified authentication service diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 35385af59743..6283382258a8 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -25,6 +25,7 @@ adyen.payout_base_url = "https://pal-test.adyen.com/" adyen.dispute_base_url = "https://ca-test.adyen.com/" adyenplatform.base_url = "https://balanceplatform-api-test.adyen.com/" airwallex.base_url = "https://api-demo.airwallex.com/" +amazonpay.base_url = "https://pay-api.amazon.com/v2" applepay.base_url = "https://apple-pay-gateway.apple.com/" authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api" bambora.base_url = "https://api.na.bambora.com" @@ -44,9 +45,11 @@ cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" deutschebank.base_url = "https://testmerch.directpos.de/rest-api" +digitalvirgo.base_url = "https://dcb-integration-service-sandbox-external.staging.digitalvirgo.pl" dlocal.base_url = "https://sandbox.dlocal.com/" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" +elavon.base_url = "https://api.demo.convergepay.com/VirtualMerchantDemo/" fiserv.base_url = "https://cert.api.fiservapps.com/" fiservemea.base_url = "https://prod.emea.api.fiservapps.com/sandbox" fiuu.base_url = "https://sandbox.merchant.razer.com/" @@ -59,7 +62,10 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" +inespay.base_url = "https://apiflow.inespay.com/san/v21" itaubank.base_url = "https://sandbox.devportal.itau.com.br/" +jpmorgan.base_url = "https://api-mock.payments.jpmorgan.com/api/v2" +jpmorgan.secondary_base_url="https://id.payments.jpmorgan.com" klarna.base_url = "https://api{{klarna_region}}.playground.klarna.com/" mifinity.base_url = "https://demo.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" @@ -68,6 +74,7 @@ multisafepay.base_url = "https://testapi.multisafepay.com/" nexinets.base_url = "https://apitest.payengine.de/v1" nexixpay.base_url = "https://xpaysandbox.nexigroup.com/api/phoenix-0.0/psp/api/v1" nmi.base_url = "https://secure.nmi.com/" +nomupay.base_url = "https://payout-api.sandbox.nomupay.com" noon.base_url = "https://api-test.noonpayments.com/" noon.key_mode = "Test" novalnet.base_url = "https://payport.novalnet.de/v2" @@ -87,6 +94,7 @@ powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" razorpay.base_url = "https://sandbox.juspay.in/" +redsys.base_url = "https://sis-t.redsys.es:25443/sis/realizarPago" shift4.base_url = "https://api.shift4.com/" signifyd.base_url = "https://api.signifyd.com/" riskified.base_url = "https://sandbox.riskified.com/api" @@ -106,6 +114,7 @@ wellsfargopayout.base_url = "https://api-sandbox.wellsfargo.com/" wise.base_url = "https://api.sandbox.transferwise.tech/" worldline.base_url = "https://eu.sandbox.api-ingenico.com/" worldpay.base_url = "https://try.access.worldpay.com/" +xendit.base_url = "https://api.xendit.co" zen.base_url = "https://api.zen-test.com/" zen.secondary_base_url = "https://secure.zen-test.com/" zsl.base_url = "https://api.sitoffalb.net/" @@ -137,6 +146,8 @@ password_validity_in_days = 90 two_factor_auth_expiry_in_secs = 300 totp_issuer_name = "Hyperswitch Integ" base_url = "https://integ.hyperswitch.io" +force_two_factor_auth = false +force_cookies = true [frm] enabled = true @@ -149,19 +160,31 @@ payout_connector_list = "stripe,wise" connectors_with_delayed_session_response = "trustpay,payme" # List of connectors which have delayed session response [mandates.supported_payment_methods] -bank_debit.ach = { connector_list = "gocardless,adyen" } # Mandate supported payment method type and connector for bank_debit -bank_debit.becs = { connector_list = "gocardless" } # Mandate supported payment method type and connector for bank_debit -bank_debit.bacs = { connector_list = "adyen" } # Mandate supported payment method type and connector for bank_debit -bank_debit.sepa = { connector_list = "gocardless,adyen" } # Mandate supported payment method type and connector for bank_debit -card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree" # Mandate supported payment method type and connector for card -card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree" # Mandate supported payment method type and connector for card -pay_later.klarna.connector_list = "adyen" # Mandate supported payment method type and connector for pay_later -wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon,bankofamerica" # Mandate supported payment method type and connector for wallets -wallet.google_pay.connector_list = "stripe,adyen,cybersource,bankofamerica" # Mandate supported payment method type and connector for wallets -wallet.paypal.connector_list = "adyen" # Mandate supported payment method type and connector for wallets -bank_redirect.ideal.connector_list = "stripe,adyen,globalpay,multisafepay" # Mandate supported payment method type and connector for bank_redirect -bank_redirect.sofort.connector_list = "stripe,adyen,globalpay" # Mandate supported payment method type and connector for bank_redirect -bank_redirect.giropay.connector_list = "adyen,globalpay,multisafepay" # Mandate supported payment method type and connector for bank_redirect +bank_debit.ach = { connector_list = "gocardless,adyen,stripe" } +bank_debit.becs = { connector_list = "gocardless,stripe,adyen" } +bank_debit.bacs = { connector_list = "stripe,gocardless" } +bank_debit.sepa = { connector_list = "gocardless,adyen,stripe,deutschebank" } +card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" +card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" +pay_later.klarna.connector_list = "adyen" +wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon,bankofamerica,nexinets,novalnet" +wallet.google_pay.connector_list = "stripe,adyen,cybersource,bankofamerica,noon,globalpay,multisafepay,novalnet" +wallet.paypal.connector_list = "adyen,globalpay,nexinets,novalnet,paypal" +wallet.momo.connector_list = "adyen" +wallet.kakao_pay.connector_list = "adyen" +wallet.go_pay.connector_list = "adyen" +wallet.gcash.connector_list = "adyen" +wallet.dana.connector_list = "adyen" +wallet.twint.connector_list = "adyen" +wallet.vipps.connector_list = "adyen" + +bank_redirect.ideal.connector_list = "stripe,adyen,globalpay,multisafepay,nexinets" +bank_redirect.sofort.connector_list = "stripe,adyen,globalpay" +bank_redirect.giropay.connector_list = "adyen,globalpay,multisafepay,nexinets" +bank_redirect.bancontact_card.connector_list="adyen,stripe" +bank_redirect.trustly.connector_list="adyen" +bank_redirect.open_banking_uk.connector_list="adyen" +bank_redirect.eps.connector_list="globalpay,nexinets" [mandates.update_mandate_supported] card.credit = { connector_list = "cybersource" } # Update Mandate supported payment method type and connector for card @@ -195,7 +218,7 @@ alfamart = { country = "ID", currency = "IDR" } ali_pay = { country = "AU,JP,HK,SG,MY,TH,ES,GB,SE,NO,AT,NL,DE,CY,CH,BE,FR,DK,FI,RO,MT,SI,GR,PT,IE,IT,CA,US", currency = "USD,EUR,GBP,JPY,AUD,SGD,CHF,SEK,NOK,NZD,THB,HKD,CAD" } ali_pay_hk = { country = "HK", currency = "HKD" } alma = { country = "FR", currency = "EUR" } -apple_pay = { country = "AU,NZ,CN,JP,HK,SG,MY,BH,AE,KW,BR,ES,GB,SE,NO,AT,NL,DE,HU,CY,LU,CH,BE,FR,DK,FI,RO,HR,LI,UA,MT,SI,GR,PT,IE,CZ,EE,LT,LV,IT,PL,IS,CA,US", currency = "AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD" } +apple_pay = { country = "AU,NZ,CN,JP,HK,SG,MY,BH,AE,KW,BR,ES,GB,SE,NO,AT,NL,DE,HU,CY,LU,CH,BE,FR,DK,FI,RO,HR,LI,UA,MT,SI,GR,PT,IE,CZ,EE,LT,LV,IT,PL,IS,CA,US", currency = "AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD,MYR" } atome = { country = "MY,SG", currency = "MYR,SGD" } bacs = { country = "GB", currency = "GBP" } bancontact_card = { country = "BE", currency = "EUR" } @@ -255,6 +278,10 @@ we_chat_pay = { country = "AU,NZ,CN,JP,HK,SG,ES,GB,SE,NO,AT,NL,DE,CY,CH,BE,FR,DK google_pay.currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" paypal.currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" +[pm_filters.bambora] +credit = { country = "US,CA", currency = "USD" } +debit = { country = "US,CA", currency = "USD" } + [pm_filters.bankofamerica] credit = { currency = "USD" } debit = { currency = "USD" } @@ -276,6 +303,10 @@ debit.currency = "USD" ali_pay.currency = "GBP,CNY" we_chat_pay.currency = "GBP,CNY" +[pm.filters.jpmorgan] +debit = { country = "CA, EU, UK, US", currency = "CAD, EUR, GBP, USD" } +credit = { country = "CA, EU, UK, US", currency = "CAD, EUR, GBP, USD" } + [pm_filters.klarna] klarna = { country = "AU,AT,BE,CA,CZ,DK,FI,FR,DE,GR,IE,IT,NL,NZ,NO,PL,PT,ES,SE,CH,GB,US", currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" } @@ -301,11 +332,23 @@ klarna = { country = "AU,AT,BE,CA,CZ,DK,FI,FR,DE,GR,IE,IT,NL,NZ,NO,PL,PT,ES,SE,C sofort = { country = "AT,BE,DE,IT,NL,ES", currency = "EUR" } [pm_filters.cybersource] -credit = { currency = "USD,GBP,EUR" } -debit = { currency = "USD,GBP,EUR" } -apple_pay = { currency = "USD,GBP,EUR" } -google_pay = { currency = "USD,GBP,EUR" } +credit = { currency = "USD,GBP,EUR,PLN" } +debit = { currency = "USD,GBP,EUR,PLN" } +apple_pay = { currency = "USD,GBP,EUR,PLN" } +google_pay = { currency = "USD,GBP,EUR,PLN" } samsung_pay = { currency = "USD,GBP,EUR" } +paze = { currency = "USD" } + +[pm_filters.nexixpay] +credit = { country = "AT,BE,CY,EE,FI,FR,DE,GR,IE,IT,LV,LT,LU,MT,NL,PT,SK,SI,ES,BG,HR,DK,GB,NO,PL,CZ,RO,SE,CH,HU", currency = "ARS,AUD,BHD,CAD,CLP,CNY,COP,HRK,CZK,DKK,HKD,HUF,INR,JPY,KZT,JOD,KRW,KWD,MYR,MXN,NGN,NOK,PHP,QAR,RUB,SAR,SGD,VND,ZAR,SEK,CHF,THB,AED,EGP,GBP,USD,TWD,BYN,RSD,AZN,RON,TRY,AOA,BGN,EUR,UAH,PLN,BRL" } +debit = { country = "AT,BE,CY,EE,FI,FR,DE,GR,IE,IT,LV,LT,LU,MT,NL,PT,SK,SI,ES,BG,HR,DK,GB,NO,PL,CZ,RO,SE,CH,HU", currency = "ARS,AUD,BHD,CAD,CLP,CNY,COP,HRK,CZK,DKK,HKD,HUF,INR,JPY,KZT,JOD,KRW,KWD,MYR,MXN,NGN,NOK,PHP,QAR,RUB,SAR,SGD,VND,ZAR,SEK,CHF,THB,AED,EGP,GBP,USD,TWD,BYN,RSD,AZN,RON,TRY,AOA,BGN,EUR,UAH,PLN,BRL" } + +[pm_filters.novalnet] +credit = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} +debit = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} +apple_pay = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} +google_pay = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} +paypal = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} [pm_filters.volt] open_banking_uk = {country = "DE,GB,AT,BE,CY,EE,ES,FI,FR,GR,HR,IE,IT,LT,LU,LV,MT,NL,PT,SI,SK,BG,CZ,DK,HU,NO,PL,RO,SE,AU,BR", currency = "EUR,GBP,DKK,NOK,PLN,SEK,AUD,BRL"} @@ -317,8 +360,10 @@ upi_collect = {country = "IN", currency = "INR"} open_banking_pis = {currency = "EUR,GBP"} [pm_filters.worldpay] -apple_pay.country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US" -google_pay.country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" +debit = { country = "AF,DZ,AW,AU,AZ,BS,BH,BD,BB,BZ,BM,BT,BO,BA,BW,BR,BN,BG,BI,KH,CA,CV,KY,CL,CO,KM,CD,CR,CZ,DK,DJ,ST,DO,EC,EG,SV,ER,ET,FK,FJ,GM,GE,GH,GI,GT,GN,GY,HT,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IL,IT,JM,JP,JO,KZ,KE,KW,LA,LB,LS,LR,LY,LT,MO,MK,MG,MW,MY,MV,MR,MU,MX,MD,MN,MA,MZ,MM,NA,NZ,NI,NG,KP,NO,AR,PK,PG,PY,PE,UY,PH,PL,GB,QA,OM,RO,RU,RW,WS,SG,ST,ZA,KR,LK,SH,SD,SR,SZ,SE,CH,SY,TW,TJ,TZ,TH,TT,TN,TR,UG,UA,US,UZ,VU,VE,VN,ZM,ZW", currency = "AFN,DZD,ANG,AWG,AUD,AZN,BSD,BHD,BDT,BBD,BZD,BMD,BTN,BOB,BAM,BWP,BRL,BND,BGN,BIF,KHR,CAD,CVE,KYD,XOF,XAF,XPF,CLP,COP,KMF,CDF,CRC,EUR,CZK,DKK,DJF,DOP,XCD,EGP,SVC,ERN,ETB,EUR,FKP,FJD,GMD,GEL,GHS,GIP,GTQ,GNF,GYD,HTG,HNL,HKD,HUF,ISK,INR,IDR,IRR,IQD,ILS,JMD,JPY,JOD,KZT,KES,KWD,LAK,LBP,LSL,LRD,LYD,MOP,MKD,MGA,MWK,MYR,MVR,MRU,MUR,MXN,MDL,MNT,MAD,MZN,MMK,NAD,NPR,NZD,NIO,NGN,KPW,NOK,ARS,PKR,PAB,PGK,PYG,PEN,UYU,PHP,PLN,GBP,QAR,OMR,RON,RUB,RWF,WST,SAR,RSD,SCR,SLL,SGD,STN,SBD,SOS,ZAR,KRW,LKR,SHP,SDG,SRD,SZL,SEK,CHF,SYP,TWD,TJS,TZS,THB,TOP,TTD,TND,TRY,TMT,AED,UGX,UAH,USD,UZS,VUV,VND,YER,CNY,ZMW,ZWL" } +credit = { country = "AF,DZ,AW,AU,AZ,BS,BH,BD,BB,BZ,BM,BT,BO,BA,BW,BR,BN,BG,BI,KH,CA,CV,KY,CL,CO,KM,CD,CR,CZ,DK,DJ,ST,DO,EC,EG,SV,ER,ET,FK,FJ,GM,GE,GH,GI,GT,GN,GY,HT,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IL,IT,JM,JP,JO,KZ,KE,KW,LA,LB,LS,LR,LY,LT,MO,MK,MG,MW,MY,MV,MR,MU,MX,MD,MN,MA,MZ,MM,NA,NZ,NI,NG,KP,NO,AR,PK,PG,PY,PE,UY,PH,PL,GB,QA,OM,RO,RU,RW,WS,SG,ST,ZA,KR,LK,SH,SD,SR,SZ,SE,CH,SY,TW,TJ,TZ,TH,TT,TN,TR,UG,UA,US,UZ,VU,VE,VN,ZM,ZW", currency = "AFN,DZD,ANG,AWG,AUD,AZN,BSD,BHD,BDT,BBD,BZD,BMD,BTN,BOB,BAM,BWP,BRL,BND,BGN,BIF,KHR,CAD,CVE,KYD,XOF,XAF,XPF,CLP,COP,KMF,CDF,CRC,EUR,CZK,DKK,DJF,DOP,XCD,EGP,SVC,ERN,ETB,EUR,FKP,FJD,GMD,GEL,GHS,GIP,GTQ,GNF,GYD,HTG,HNL,HKD,HUF,ISK,INR,IDR,IRR,IQD,ILS,JMD,JPY,JOD,KZT,KES,KWD,LAK,LBP,LSL,LRD,LYD,MOP,MKD,MGA,MWK,MYR,MVR,MRU,MUR,MXN,MDL,MNT,MAD,MZN,MMK,NAD,NPR,NZD,NIO,NGN,KPW,NOK,ARS,PKR,PAB,PGK,PYG,PEN,UYU,PHP,PLN,GBP,QAR,OMR,RON,RUB,RWF,WST,SAR,RSD,SCR,SLL,SGD,STN,SBD,SOS,ZAR,KRW,LKR,SHP,SDG,SRD,SZL,SEK,CHF,SYP,TWD,TJS,TZS,THB,TOP,TTD,TND,TRY,TMT,AED,UGX,UAH,USD,UZS,VUV,VND,YER,CNY,ZMW,ZWL" } +google_pay = { country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" } +apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US" } [pm_filters.zen] boleto = { country = "BR", currency = "BRL" } @@ -333,6 +378,9 @@ red_pagos = { country = "UY", currency = "UYU" } [pm_filters.zsl] local_bank_transfer = { country = "CN", currency = "CNY" } +[pm_filters.fiuu] +duit_now = { country ="MY", currency = "MYR" } + [payout_method_filters.adyenplatform] sepa = { country = "ES,SK,AT,NL,DE,BE,FR,FI,PT,IE,EE,LT,LV,IT,CZ,DE,HU,NO,PL,SE,GB,CH" , currency = "EUR,CZK,DKK,HUF,NOK,PLN,SEK,GBP,CHF" } @@ -350,6 +398,7 @@ nmi.payment_method = "card" payme.payment_method = "card" deutschebank = { payment_method = "bank_debit" } paybox = { payment_method = "card" } +nexixpay = { payment_method = "card" } #tokenization configuration which describe token lifetime and payment method for specific connector [tokenization] @@ -370,7 +419,7 @@ outgoing_enabled = true connectors_with_webhook_source_verification_call = "paypal" # List of connectors which has additional source verification api-call [unmasked_headers] -keys = "accept-language,user-agent" +keys = "accept-language,user-agent,x-profile-id" [saved_payment_methods] sdk_eligible_payment_methods = "card" @@ -382,4 +431,7 @@ connector_list = "" card_networks = "Visa, AmericanExpress, Mastercard" [network_tokenization_supported_connectors] -connector_list = "cybersource" \ No newline at end of file +connector_list = "cybersource" + +[platform] +enabled = true diff --git a/config/deployments/production.toml b/config/deployments/production.toml index cab8317dfa45..3537834fd07e 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -28,7 +28,8 @@ adyen.base_url = "https://{{merchant_endpoint_prefix}}-checkout-live.adyenpaymen adyen.payout_base_url = "https://{{merchant_endpoint_prefix}}-pal-live.adyenpayments.com/" adyen.dispute_base_url = "https://{{merchant_endpoint_prefix}}-ca-live.adyen.com/" adyenplatform.base_url = "https://balanceplatform-api-live.adyen.com/" -airwallex.base_url = "https://api-demo.airwallex.com/" +airwallex.base_url = "https://api.airwallex.com/" +amazonpay.base_url = "https://pay-api.amazon.com/v2" applepay.base_url = "https://apple-pay-gateway.apple.com/" authorizedotnet.base_url = "https://api.authorize.net/xml/v1/request.api" bambora.base_url = "https://api.na.bambora.com" @@ -47,10 +48,12 @@ coinbase.base_url = "https://api.commerce.coinbase.com" cryptopay.base_url = "https://business.cryptopay.me/" cybersource.base_url = "https://api.cybersource.com/" datatrans.base_url = "https://api.datatrans.com/" -dlocal.base_url = "https://sandbox.dlocal.com/" deutschebank.base_url = "https://merch.directpos.de/rest-api" +digitalvirgo.base_url = "https://dcb-integration-service-sandbox-external.staging.digitalvirgo.pl" +dlocal.base_url = "https://sandbox.dlocal.com/" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" +elavon.base_url = "https://api.convergepay.com/VirtualMerchant/" fiserv.base_url = "https://cert.api.fiservapps.com/" fiservemea.base_url = "https://prod.emea.api.fiservapps.com" fiuu.base_url = "https://pay.merchant.razer.com/" @@ -63,7 +66,10 @@ gocardless.base_url = "https://api.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://iata-pay.iata.org/api/v1" +inespay.base_url = "https://apiflow.inespay.com/san/v21" itaubank.base_url = "https://secure.api.itau/" +jpmorgan.base_url = "https://api-ms.payments.jpmorgan.com/api/v2" +jpmorgan.secondary_base_url="https://id.payments.jpmorgan.com" klarna.base_url = "https://api{{klarna_region}}.klarna.com/" mifinity.base_url = "https://secure.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" @@ -72,6 +78,7 @@ multisafepay.base_url = "https://testapi.multisafepay.com/" nexinets.base_url = "https://api.payengine.de/v1" nexixpay.base_url = "https://xpay.nexigroup.com/api/phoenix-0.0/psp/api/v1" nmi.base_url = "https://secure.nmi.com/" +nomupay.base_url = "https://payout-api.nomupay.com" noon.base_url = "https://api.noonpayments.com/" noon.key_mode = "Live" novalnet.base_url = "https://payport.novalnet.de/v2" @@ -91,6 +98,7 @@ powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" razorpay.base_url = "https://api.juspay.in" +redsys.base_url = "https://sis.redsys.es:25443/sis/realizarPago" riskified.base_url = "https://wh.riskified.com/api/" shift4.base_url = "https://api.shift4.com/" signifyd.base_url = "https://api.signifyd.com/" @@ -110,6 +118,7 @@ wellsfargopayout.base_url = "https://api.wellsfargo.com/" wise.base_url = "https://api.sandbox.transferwise.tech/" worldline.base_url = "https://eu.sandbox.api-ingenico.com/" worldpay.base_url = "https://try.access.worldpay.com/" +xendit.base_url = "https://api.xendit.co" zen.base_url = "https://api.zen.com/" zen.secondary_base_url = "https://secure.zen.com/" zsl.base_url = "https://apirh.prodoffalb.net/" @@ -144,29 +153,45 @@ password_validity_in_days = 90 two_factor_auth_expiry_in_secs = 300 totp_issuer_name = "Hyperswitch Production" base_url = "https://live.hyperswitch.io" +force_two_factor_auth = true +force_cookies = false [frm] enabled = false [mandates.supported_payment_methods] -bank_debit.ach = { connector_list = "gocardless,adyen" } # Mandate supported payment method type and connector for bank_debit -bank_debit.becs = { connector_list = "gocardless" } # Mandate supported payment method type and connector for bank_debit -bank_debit.bacs = { connector_list = "adyen" } # Mandate supported payment method type and connector for bank_debit -bank_debit.sepa = { connector_list = "gocardless,adyen" } # Mandate supported payment method type and connector for bank_debit -card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree" # Mandate supported payment method type and connector for card -card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree" # Mandate supported payment method type and connector for card -pay_later.klarna.connector_list = "adyen" # Mandate supported payment method type and connector for pay_later -wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon,bankofamerica" # Mandate supported payment method type and connector for wallets -wallet.google_pay.connector_list = "stripe,adyen,cybersource,bankofamerica" # Mandate supported payment method type and connector for wallets -wallet.paypal.connector_list = "adyen" # Mandate supported payment method type and connector for wallets -bank_redirect.ideal.connector_list = "stripe,adyen,globalpay,multisafepay" # Mandate supported payment method type and connector for bank_redirect -bank_redirect.sofort.connector_list = "stripe,adyen,globalpay" # Mandate supported payment method type and connector for bank_redirect -bank_redirect.giropay.connector_list = "adyen,globalpay,multisafepay" # Mandate supported payment method type and connector for bank_redirect +bank_debit.ach = { connector_list = "gocardless,adyen,stripe" } +bank_debit.becs = { connector_list = "gocardless,stripe,adyen" } +bank_debit.bacs = { connector_list = "stripe,gocardless" } +bank_debit.sepa = { connector_list = "gocardless,adyen,stripe,deutschebank" } +card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" +card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" +pay_later.klarna.connector_list = "adyen" +wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon,bankofamerica,nexinets,novalnet" +wallet.google_pay.connector_list = "stripe,adyen,cybersource,bankofamerica,noon,globalpay,multisafepay,novalnet" +wallet.paypal.connector_list = "adyen,globalpay,nexinets,novalnet,paypal" +wallet.momo.connector_list = "adyen" +wallet.kakao_pay.connector_list = "adyen" +wallet.go_pay.connector_list = "adyen" +wallet.gcash.connector_list = "adyen" +wallet.dana.connector_list = "adyen" +wallet.twint.connector_list = "adyen" +wallet.vipps.connector_list = "adyen" + +bank_redirect.ideal.connector_list = "stripe,adyen,globalpay,multisafepay,nexinets" +bank_redirect.sofort.connector_list = "stripe,adyen,globalpay" +bank_redirect.giropay.connector_list = "adyen,globalpay,multisafepay,nexinets" +bank_redirect.bancontact_card.connector_list="adyen,stripe" +bank_redirect.trustly.connector_list="adyen" +bank_redirect.open_banking_uk.connector_list="adyen" +bank_redirect.eps.connector_list="globalpay,nexinets" [mandates.update_mandate_supported] card.credit = { connector_list = "cybersource" } # Update Mandate supported payment method type and connector for card card.debit = { connector_list = "cybersource" } # Update Mandate supported payment method type and connector for card +[network_transaction_id_supported_connectors] +connector_list = "adyen" [payouts] payout_eligibility = true # Defaults the eligibility of a payout method to true in case connector does not provide checks for payout eligibility @@ -208,7 +233,7 @@ alfamart = { country = "ID", currency = "IDR" } ali_pay = { country = "AU,JP,HK,SG,MY,TH,ES,GB,SE,NO,AT,NL,DE,CY,CH,BE,FR,DK,FI,RO,MT,SI,GR,PT,IE,IT,CA,US", currency = "USD,EUR,GBP,JPY,AUD,SGD,CHF,SEK,NOK,NZD,THB,HKD,CAD" } ali_pay_hk = { country = "HK", currency = "HKD" } alma = { country = "FR", currency = "EUR" } -apple_pay = { country = "AE,AM,AR,AT,AU,AZ,BE,BG,BH,BR,BY,CA,CH,CN,CO,CR,CY,CZ,DE,DK,EE,ES,FI,FO,FR,GB,GE,GG,GL,GR,HK,HR,HU,IE,IL,IM,IS,IT,JE,JO,JP,KW,KZ,LI,LT,LU,LV,MC,MD,ME,MO,MT,MX,MY,NL,NO,NZ,PE,PL,PS,PT,QA,RO,RS,SA,SE,SG,SI,SK,SM,TW,UA,GB,UM,US", currency = "AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD" } +apple_pay = { country = "AE,AM,AR,AT,AU,AZ,BE,BG,BH,BR,BY,CA,CH,CN,CO,CR,CY,CZ,DE,DK,EE,ES,FI,FO,FR,GB,GE,GG,GL,GR,HK,HR,HU,IE,IL,IM,IS,IT,JE,JO,JP,KW,KZ,LI,LT,LU,LV,MC,MD,ME,MO,MT,MX,MY,NL,NO,NZ,PE,PL,PS,PT,QA,RO,RS,SA,SE,SG,SI,SK,SM,TW,UA,GB,UM,US", currency = "AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD,MYR" } atome = { country = "MY,SG", currency = "MYR,SGD" } bacs = { country = "GB", currency = "GBP" } bancontact_card = { country = "BE", currency = "EUR" } @@ -267,6 +292,10 @@ we_chat_pay = { country = "AU,NZ,CN,JP,HK,SG,ES,GB,SE,NO,AT,NL,DE,CY,CH,BE,FR,DK google_pay.currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" paypal.currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" +[pm_filters.bambora] +credit = { country = "US,CA", currency = "USD" } +debit = { country = "US,CA", currency = "USD" } + [pm_filters.bankofamerica] credit = { currency = "USD" } debit = { currency = "USD" } @@ -275,11 +304,23 @@ google_pay = { currency = "USD" } [pm_filters.cybersource] -credit = { currency = "USD,GBP,EUR" } -debit = { currency = "USD,GBP,EUR" } -apple_pay = { currency = "USD,GBP,EUR" } -google_pay = { currency = "USD,GBP,EUR" } +credit = { currency = "USD,GBP,EUR,PLN" } +debit = { currency = "USD,GBP,EUR,PLN" } +apple_pay = { currency = "USD,GBP,EUR,PLN" } +google_pay = { currency = "USD,GBP,EUR,PLN" } samsung_pay = { currency = "USD,GBP,EUR" } +paze = { currency = "USD" } + +[pm_filters.nexixpay] +credit = { country = "AT,BE,CY,EE,FI,FR,DE,GR,IE,IT,LV,LT,LU,MT,NL,PT,SK,SI,ES,BG,HR,DK,GB,NO,PL,CZ,RO,SE,CH,HU", currency = "ARS,AUD,BHD,CAD,CLP,CNY,COP,HRK,CZK,DKK,HKD,HUF,INR,JPY,KZT,JOD,KRW,KWD,MYR,MXN,NGN,NOK,PHP,QAR,RUB,SAR,SGD,VND,ZAR,SEK,CHF,THB,AED,EGP,GBP,USD,TWD,BYN,RSD,AZN,RON,TRY,AOA,BGN,EUR,UAH,PLN,BRL" } +debit = { country = "AT,BE,CY,EE,FI,FR,DE,GR,IE,IT,LV,LT,LU,MT,NL,PT,SK,SI,ES,BG,HR,DK,GB,NO,PL,CZ,RO,SE,CH,HU", currency = "ARS,AUD,BHD,CAD,CLP,CNY,COP,HRK,CZK,DKK,HKD,HUF,INR,JPY,KZT,JOD,KRW,KWD,MYR,MXN,NGN,NOK,PHP,QAR,RUB,SAR,SGD,VND,ZAR,SEK,CHF,THB,AED,EGP,GBP,USD,TWD,BYN,RSD,AZN,RON,TRY,AOA,BGN,EUR,UAH,PLN,BRL" } + +[pm_filters.novalnet] +credit = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} +debit = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} +apple_pay = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} +google_pay = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} +paypal = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} [pm_filters.braintree] paypal.currency = "AUD,BRL,CAD,CNY,CZK,DKK,EUR,HKD,HUF,ILS,JPY,MYR,MXN,TWD,NZD,NOK,PHP,PLN,GBP,RUB,SGD,SEK,CHF,THB,USD" @@ -296,6 +337,10 @@ debit.currency = "USD" ali_pay.currency = "GBP,CNY" we_chat_pay.currency = "GBP,CNY" +[pm.filters.jpmorgan] +debit = { country = "CA, EU, UK, US", currency = "CAD, EUR, GBP, USD" } +credit = { country = "CA, EU, UK, US", currency = "CAD, EUR, GBP, USD" } + [pm_filters.klarna] klarna = { country = "AU,AT,BE,CA,CZ,DK,FI,FR,DE,GR,IE,IT,NL,NZ,NO,PL,PT,ES,SE,CH,GB,US", currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" } @@ -330,8 +375,10 @@ upi_collect = {country = "IN", currency = "INR"} open_banking_pis = {currency = "EUR,GBP"} [pm_filters.worldpay] -apple_pay.country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US" -google_pay.country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" +debit = { country = "AF,DZ,AW,AU,AZ,BS,BH,BD,BB,BZ,BM,BT,BO,BA,BW,BR,BN,BG,BI,KH,CA,CV,KY,CL,CO,KM,CD,CR,CZ,DK,DJ,ST,DO,EC,EG,SV,ER,ET,FK,FJ,GM,GE,GH,GI,GT,GN,GY,HT,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IL,IT,JM,JP,JO,KZ,KE,KW,LA,LB,LS,LR,LY,LT,MO,MK,MG,MW,MY,MV,MR,MU,MX,MD,MN,MA,MZ,MM,NA,NZ,NI,NG,KP,NO,AR,PK,PG,PY,PE,UY,PH,PL,GB,QA,OM,RO,RU,RW,WS,SG,ST,ZA,KR,LK,SH,SD,SR,SZ,SE,CH,SY,TW,TJ,TZ,TH,TT,TN,TR,UG,UA,US,UZ,VU,VE,VN,ZM,ZW", currency = "AFN,DZD,ANG,AWG,AUD,AZN,BSD,BHD,BDT,BBD,BZD,BMD,BTN,BOB,BAM,BWP,BRL,BND,BGN,BIF,KHR,CAD,CVE,KYD,XOF,XAF,XPF,CLP,COP,KMF,CDF,CRC,EUR,CZK,DKK,DJF,DOP,XCD,EGP,SVC,ERN,ETB,EUR,FKP,FJD,GMD,GEL,GHS,GIP,GTQ,GNF,GYD,HTG,HNL,HKD,HUF,ISK,INR,IDR,IRR,IQD,ILS,JMD,JPY,JOD,KZT,KES,KWD,LAK,LBP,LSL,LRD,LYD,MOP,MKD,MGA,MWK,MYR,MVR,MRU,MUR,MXN,MDL,MNT,MAD,MZN,MMK,NAD,NPR,NZD,NIO,NGN,KPW,NOK,ARS,PKR,PAB,PGK,PYG,PEN,UYU,PHP,PLN,GBP,QAR,OMR,RON,RUB,RWF,WST,SAR,RSD,SCR,SLL,SGD,STN,SBD,SOS,ZAR,KRW,LKR,SHP,SDG,SRD,SZL,SEK,CHF,SYP,TWD,TJS,TZS,THB,TOP,TTD,TND,TRY,TMT,AED,UGX,UAH,USD,UZS,VUV,VND,YER,CNY,ZMW,ZWL" } +credit = { country = "AF,DZ,AW,AU,AZ,BS,BH,BD,BB,BZ,BM,BT,BO,BA,BW,BR,BN,BG,BI,KH,CA,CV,KY,CL,CO,KM,CD,CR,CZ,DK,DJ,ST,DO,EC,EG,SV,ER,ET,FK,FJ,GM,GE,GH,GI,GT,GN,GY,HT,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IL,IT,JM,JP,JO,KZ,KE,KW,LA,LB,LS,LR,LY,LT,MO,MK,MG,MW,MY,MV,MR,MU,MX,MD,MN,MA,MZ,MM,NA,NZ,NI,NG,KP,NO,AR,PK,PG,PY,PE,UY,PH,PL,GB,QA,OM,RO,RU,RW,WS,SG,ST,ZA,KR,LK,SH,SD,SR,SZ,SE,CH,SY,TW,TJ,TZ,TH,TT,TN,TR,UG,UA,US,UZ,VU,VE,VN,ZM,ZW", currency = "AFN,DZD,ANG,AWG,AUD,AZN,BSD,BHD,BDT,BBD,BZD,BMD,BTN,BOB,BAM,BWP,BRL,BND,BGN,BIF,KHR,CAD,CVE,KYD,XOF,XAF,XPF,CLP,COP,KMF,CDF,CRC,EUR,CZK,DKK,DJF,DOP,XCD,EGP,SVC,ERN,ETB,EUR,FKP,FJD,GMD,GEL,GHS,GIP,GTQ,GNF,GYD,HTG,HNL,HKD,HUF,ISK,INR,IDR,IRR,IQD,ILS,JMD,JPY,JOD,KZT,KES,KWD,LAK,LBP,LSL,LRD,LYD,MOP,MKD,MGA,MWK,MYR,MVR,MRU,MUR,MXN,MDL,MNT,MAD,MZN,MMK,NAD,NPR,NZD,NIO,NGN,KPW,NOK,ARS,PKR,PAB,PGK,PYG,PEN,UYU,PHP,PLN,GBP,QAR,OMR,RON,RUB,RWF,WST,SAR,RSD,SCR,SLL,SGD,STN,SBD,SOS,ZAR,KRW,LKR,SHP,SDG,SRD,SZL,SEK,CHF,SYP,TWD,TJS,TZS,THB,TOP,TTD,TND,TRY,TMT,AED,UGX,UAH,USD,UZS,VUV,VND,YER,CNY,ZMW,ZWL" } +google_pay = { country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" } +apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US" } [pm_filters.zen] boleto = { country = "BR", currency = "BRL" } @@ -346,6 +393,10 @@ red_pagos = { country = "UY", currency = "UYU" } [pm_filters.zsl] local_bank_transfer = { country = "CN", currency = "CNY" } + +[pm_filters.fiuu] +duit_now = { country ="MY", currency = "MYR" } + [payout_method_filters.adyenplatform] sepa = { country = "ES,SK,AT,NL,DE,BE,FR,FI,PT,IE,EE,LT,LV,IT,CZ,DE,HU,NO,PL,SE,GB,CH" , currency = "EUR,CZK,DKK,HUF,NOK,PLN,SEK,GBP,CHF" } @@ -363,6 +414,7 @@ nmi.payment_method = "card" payme.payment_method = "card" deutschebank = { payment_method = "bank_debit" } paybox = { payment_method = "card" } +nexixpay = { payment_method = "card" } #tokenization configuration which describe token lifetime and payment method for specific connector [tokenization] @@ -383,7 +435,7 @@ outgoing_enabled = true connectors_with_webhook_source_verification_call = "paypal" # List of connectors which has additional source verification api-call [unmasked_headers] -keys = "accept-language,user-agent" +keys = "accept-language,user-agent,x-profile-id" [saved_payment_methods] sdk_eligible_payment_methods = "card" @@ -395,4 +447,7 @@ connector_list = "" card_networks = "Visa, AmericanExpress, Mastercard" [network_tokenization_supported_connectors] -connector_list = "cybersource" \ No newline at end of file +connector_list = "cybersource" + +[platform] +enabled = false diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 78972c0b2907..fcfadb339d9d 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -29,6 +29,7 @@ adyen.payout_base_url = "https://pal-test.adyen.com/" adyen.dispute_base_url = "https://ca-test.adyen.com/" adyenplatform.base_url = "https://balanceplatform-api-test.adyen.com/" airwallex.base_url = "https://api-demo.airwallex.com/" +amazonpay.base_url = "https://pay-api.amazon.com/v2" applepay.base_url = "https://apple-pay-gateway.apple.com/" authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api" bambora.base_url = "https://api.na.bambora.com" @@ -47,10 +48,12 @@ coinbase.base_url = "https://api.commerce.coinbase.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" -dlocal.base_url = "https://sandbox.dlocal.com/" deutschebank.base_url = "https://testmerch.directpos.de/rest-api" +digitalvirgo.base_url = "https://dcb-integration-service-sandbox-external.staging.digitalvirgo.pl" +dlocal.base_url = "https://sandbox.dlocal.com/" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" +elavon.base_url = "https://api.demo.convergepay.com/VirtualMerchantDemo/" fiserv.base_url = "https://cert.api.fiservapps.com/" fiservemea.base_url = "https://prod.emea.api.fiservapps.com/sandbox" fiuu.base_url = "https://sandbox.merchant.razer.com/" @@ -63,7 +66,10 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" +inespay.base_url = "https://apiflow.inespay.com/san/v21" itaubank.base_url = "https://sandbox.devportal.itau.com.br/" +jpmorgan.base_url = "https://api-mock.payments.jpmorgan.com/api/v2" +jpmorgan.secondary_base_url="https://id.payments.jpmorgan.com" klarna.base_url = "https://api{{klarna_region}}.playground.klarna.com/" mifinity.base_url = "https://demo.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" @@ -72,6 +78,7 @@ multisafepay.base_url = "https://testapi.multisafepay.com/" nexinets.base_url = "https://apitest.payengine.de/v1" nexixpay.base_url = "https://xpaysandbox.nexigroup.com/api/phoenix-0.0/psp/api/v1" nmi.base_url = "https://secure.nmi.com/" +nomupay.base_url = "https://payout-api.sandbox.nomupay.com" noon.base_url = "https://api-test.noonpayments.com/" noon.key_mode = "Test" novalnet.base_url = "https://payport.novalnet.de/v2" @@ -91,6 +98,7 @@ powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" razorpay.base_url = "https://sandbox.juspay.in/" +redsys.base_url = "https://sis-t.redsys.es:25443/sis/realizarPago" riskified.base_url = "https://sandbox.riskified.com/api" shift4.base_url = "https://api.shift4.com/" signifyd.base_url = "https://api.signifyd.com/" @@ -110,6 +118,7 @@ wellsfargopayout.base_url = "https://api-sandbox.wellsfargo.com/" wise.base_url = "https://api.sandbox.transferwise.tech/" worldline.base_url = "https://eu.sandbox.api-ingenico.com/" worldpay.base_url = "https://try.access.worldpay.com/" +xendit.base_url = "https://api.xendit.co" zen.base_url = "https://api.zen-test.com/" zen.secondary_base_url = "https://secure.zen-test.com/" zsl.base_url = "https://api.sitoffalb.net/" @@ -144,24 +153,38 @@ password_validity_in_days = 90 two_factor_auth_expiry_in_secs = 300 totp_issuer_name = "Hyperswitch Sandbox" base_url = "https://app.hyperswitch.io" +force_two_factor_auth = false +force_cookies = false [frm] enabled = true [mandates.supported_payment_methods] -bank_debit.ach = { connector_list = "gocardless,adyen" } # Mandate supported payment method type and connector for bank_debit -bank_debit.becs = { connector_list = "gocardless" } # Mandate supported payment method type and connector for bank_debit -bank_debit.bacs = { connector_list = "adyen" } # Mandate supported payment method type and connector for bank_debit -bank_debit.sepa = { connector_list = "gocardless,adyen" } # Mandate supported payment method type and connector for bank_debit -card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree" # Mandate supported payment method type and connector for card -card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree" # Mandate supported payment method type and connector for card -pay_later.klarna.connector_list = "adyen" # Mandate supported payment method type and connector for pay_later -wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon,bankofamerica" # Mandate supported payment method type and connector for wallets -wallet.google_pay.connector_list = "stripe,adyen,cybersource,bankofamerica" # Mandate supported payment method type and connector for wallets -wallet.paypal.connector_list = "adyen" # Mandate supported payment method type and connector for wallets -bank_redirect.ideal.connector_list = "stripe,adyen,globalpay,multisafepay" # Mandate supported payment method type and connector for bank_redirect -bank_redirect.sofort.connector_list = "stripe,adyen,globalpay" # Mandate supported payment method type and connector for bank_redirect -bank_redirect.giropay.connector_list = "adyen,globalpay,multisafepay" # Mandate supported payment method type and connector for bank_redirect +bank_debit.ach = { connector_list = "gocardless,adyen,stripe" } +bank_debit.becs = { connector_list = "gocardless,stripe,adyen" } +bank_debit.bacs = { connector_list = "stripe,gocardless" } +bank_debit.sepa = { connector_list = "gocardless,adyen,stripe,deutschebank" } +card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" +card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" +pay_later.klarna.connector_list = "adyen" +wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon,bankofamerica,nexinets,novalnet" +wallet.google_pay.connector_list = "stripe,adyen,cybersource,bankofamerica,noon,globalpay,multisafepay,novalnet" +wallet.paypal.connector_list = "adyen,globalpay,nexinets,novalnet,paypal" +wallet.momo.connector_list = "adyen" +wallet.kakao_pay.connector_list = "adyen" +wallet.go_pay.connector_list = "adyen" +wallet.gcash.connector_list = "adyen" +wallet.dana.connector_list = "adyen" +wallet.twint.connector_list = "adyen" +wallet.vipps.connector_list = "adyen" + +bank_redirect.ideal.connector_list = "stripe,adyen,globalpay,multisafepay,nexinets" +bank_redirect.sofort.connector_list = "stripe,adyen,globalpay" +bank_redirect.giropay.connector_list = "adyen,globalpay,multisafepay,nexinets" +bank_redirect.bancontact_card.connector_list="adyen,stripe" +bank_redirect.trustly.connector_list="adyen" +bank_redirect.open_banking_uk.connector_list="adyen" +bank_redirect.eps.connector_list="globalpay,nexinets" [mandates.update_mandate_supported] card.credit = { connector_list = "cybersource" } # Update Mandate supported payment method type and connector for card @@ -211,7 +234,7 @@ alfamart = { country = "ID", currency = "IDR" } ali_pay = { country = "AU,JP,HK,SG,MY,TH,ES,GB,SE,NO,AT,NL,DE,CY,CH,BE,FR,DK,FI,RO,MT,SI,GR,PT,IE,IT,CA,US", currency = "USD,EUR,GBP,JPY,AUD,SGD,CHF,SEK,NOK,NZD,THB,HKD,CAD" } ali_pay_hk = { country = "HK", currency = "HKD" } alma = { country = "FR", currency = "EUR" } -apple_pay = { country = "AU,NZ,CN,JP,HK,SG,MY,BH,AE,KW,BR,ES,GB,SE,NO,AT,NL,DE,HU,CY,LU,CH,BE,FR,DK,FI,RO,HR,LI,UA,MT,SI,GR,PT,IE,CZ,EE,LT,LV,IT,PL,IS,CA,US", currency = "AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD" } +apple_pay = { country = "AU,NZ,CN,JP,HK,SG,MY,BH,AE,KW,BR,ES,GB,SE,NO,AT,NL,DE,HU,CY,LU,CH,BE,FR,DK,FI,RO,HR,LI,UA,MT,SI,GR,PT,IE,CZ,EE,LT,LV,IT,PL,IS,CA,US", currency = "AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD,MYR" } atome = { country = "MY,SG", currency = "MYR,SGD" } bacs = { country = "GB", currency = "GBP" } bancontact_card = { country = "BE", currency = "EUR" } @@ -271,6 +294,10 @@ pix = { country = "BR", currency = "BRL" } google_pay.currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" paypal.currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" +[pm_filters.bambora] +credit = { country = "US,CA", currency = "USD" } +debit = { country = "US,CA", currency = "USD" } + [pm_filters.bankofamerica] credit = { currency = "USD" } debit = { currency = "USD" } @@ -278,11 +305,23 @@ apple_pay = { currency = "USD" } google_pay = { currency = "USD" } [pm_filters.cybersource] -credit = { currency = "USD,GBP,EUR" } -debit = { currency = "USD,GBP,EUR" } -apple_pay = { currency = "USD,GBP,EUR" } -google_pay = { currency = "USD,GBP,EUR" } +credit = { currency = "USD,GBP,EUR,PLN" } +debit = { currency = "USD,GBP,EUR,PLN" } +apple_pay = { currency = "USD,GBP,EUR,PLN" } +google_pay = { currency = "USD,GBP,EUR,PLN" } samsung_pay = { currency = "USD,GBP,EUR" } +paze = { currency = "USD" } + +[pm_filters.nexixpay] +credit = { country = "AT,BE,CY,EE,FI,FR,DE,GR,IE,IT,LV,LT,LU,MT,NL,PT,SK,SI,ES,BG,HR,DK,GB,NO,PL,CZ,RO,SE,CH,HU", currency = "ARS,AUD,BHD,CAD,CLP,CNY,COP,HRK,CZK,DKK,HKD,HUF,INR,JPY,KZT,JOD,KRW,KWD,MYR,MXN,NGN,NOK,PHP,QAR,RUB,SAR,SGD,VND,ZAR,SEK,CHF,THB,AED,EGP,GBP,USD,TWD,BYN,RSD,AZN,RON,TRY,AOA,BGN,EUR,UAH,PLN,BRL" } +debit = { country = "AT,BE,CY,EE,FI,FR,DE,GR,IE,IT,LV,LT,LU,MT,NL,PT,SK,SI,ES,BG,HR,DK,GB,NO,PL,CZ,RO,SE,CH,HU", currency = "ARS,AUD,BHD,CAD,CLP,CNY,COP,HRK,CZK,DKK,HKD,HUF,INR,JPY,KZT,JOD,KRW,KWD,MYR,MXN,NGN,NOK,PHP,QAR,RUB,SAR,SGD,VND,ZAR,SEK,CHF,THB,AED,EGP,GBP,USD,TWD,BYN,RSD,AZN,RON,TRY,AOA,BGN,EUR,UAH,PLN,BRL" } + +[pm_filters.novalnet] +credit = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} +debit = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} +apple_pay = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} +google_pay = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} +paypal = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} [pm_filters.braintree] paypal.currency = "AUD,BRL,CAD,CNY,CZK,DKK,EUR,HKD,HUF,ILS,JPY,MYR,MXN,TWD,NZD,NOK,PHP,PLN,GBP,RUB,SGD,SEK,CHF,THB,USD" @@ -299,6 +338,10 @@ debit.currency = "USD" ali_pay.currency = "GBP,CNY" we_chat_pay.currency = "GBP,CNY" +[pm.filters.jpmorgan] +debit = { country = "CA, EU, UK, US", currency = "CAD, EUR, GBP, USD" } +credit = { country = "CA, EU, UK, US", currency = "CAD, EUR, GBP, USD" } + [pm_filters.klarna] klarna = { country = "AU,AT,BE,CA,CZ,DK,FI,FR,DE,GR,IE,IT,NL,NZ,NO,PL,PT,ES,SE,CH,GB,US", currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" } @@ -334,8 +377,10 @@ upi_collect = {country = "IN", currency = "INR"} open_banking_pis = {currency = "EUR,GBP"} [pm_filters.worldpay] -apple_pay.country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US" -google_pay.country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" +debit = { country = "AF,DZ,AW,AU,AZ,BS,BH,BD,BB,BZ,BM,BT,BO,BA,BW,BR,BN,BG,BI,KH,CA,CV,KY,CL,CO,KM,CD,CR,CZ,DK,DJ,ST,DO,EC,EG,SV,ER,ET,FK,FJ,GM,GE,GH,GI,GT,GN,GY,HT,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IL,IT,JM,JP,JO,KZ,KE,KW,LA,LB,LS,LR,LY,LT,MO,MK,MG,MW,MY,MV,MR,MU,MX,MD,MN,MA,MZ,MM,NA,NZ,NI,NG,KP,NO,AR,PK,PG,PY,PE,UY,PH,PL,GB,QA,OM,RO,RU,RW,WS,SG,ST,ZA,KR,LK,SH,SD,SR,SZ,SE,CH,SY,TW,TJ,TZ,TH,TT,TN,TR,UG,UA,US,UZ,VU,VE,VN,ZM,ZW", currency = "AFN,DZD,ANG,AWG,AUD,AZN,BSD,BHD,BDT,BBD,BZD,BMD,BTN,BOB,BAM,BWP,BRL,BND,BGN,BIF,KHR,CAD,CVE,KYD,XOF,XAF,XPF,CLP,COP,KMF,CDF,CRC,EUR,CZK,DKK,DJF,DOP,XCD,EGP,SVC,ERN,ETB,EUR,FKP,FJD,GMD,GEL,GHS,GIP,GTQ,GNF,GYD,HTG,HNL,HKD,HUF,ISK,INR,IDR,IRR,IQD,ILS,JMD,JPY,JOD,KZT,KES,KWD,LAK,LBP,LSL,LRD,LYD,MOP,MKD,MGA,MWK,MYR,MVR,MRU,MUR,MXN,MDL,MNT,MAD,MZN,MMK,NAD,NPR,NZD,NIO,NGN,KPW,NOK,ARS,PKR,PAB,PGK,PYG,PEN,UYU,PHP,PLN,GBP,QAR,OMR,RON,RUB,RWF,WST,SAR,RSD,SCR,SLL,SGD,STN,SBD,SOS,ZAR,KRW,LKR,SHP,SDG,SRD,SZL,SEK,CHF,SYP,TWD,TJS,TZS,THB,TOP,TTD,TND,TRY,TMT,AED,UGX,UAH,USD,UZS,VUV,VND,YER,CNY,ZMW,ZWL" } +credit = { country = "AF,DZ,AW,AU,AZ,BS,BH,BD,BB,BZ,BM,BT,BO,BA,BW,BR,BN,BG,BI,KH,CA,CV,KY,CL,CO,KM,CD,CR,CZ,DK,DJ,ST,DO,EC,EG,SV,ER,ET,FK,FJ,GM,GE,GH,GI,GT,GN,GY,HT,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IL,IT,JM,JP,JO,KZ,KE,KW,LA,LB,LS,LR,LY,LT,MO,MK,MG,MW,MY,MV,MR,MU,MX,MD,MN,MA,MZ,MM,NA,NZ,NI,NG,KP,NO,AR,PK,PG,PY,PE,UY,PH,PL,GB,QA,OM,RO,RU,RW,WS,SG,ST,ZA,KR,LK,SH,SD,SR,SZ,SE,CH,SY,TW,TJ,TZ,TH,TT,TN,TR,UG,UA,US,UZ,VU,VE,VN,ZM,ZW", currency = "AFN,DZD,ANG,AWG,AUD,AZN,BSD,BHD,BDT,BBD,BZD,BMD,BTN,BOB,BAM,BWP,BRL,BND,BGN,BIF,KHR,CAD,CVE,KYD,XOF,XAF,XPF,CLP,COP,KMF,CDF,CRC,EUR,CZK,DKK,DJF,DOP,XCD,EGP,SVC,ERN,ETB,EUR,FKP,FJD,GMD,GEL,GHS,GIP,GTQ,GNF,GYD,HTG,HNL,HKD,HUF,ISK,INR,IDR,IRR,IQD,ILS,JMD,JPY,JOD,KZT,KES,KWD,LAK,LBP,LSL,LRD,LYD,MOP,MKD,MGA,MWK,MYR,MVR,MRU,MUR,MXN,MDL,MNT,MAD,MZN,MMK,NAD,NPR,NZD,NIO,NGN,KPW,NOK,ARS,PKR,PAB,PGK,PYG,PEN,UYU,PHP,PLN,GBP,QAR,OMR,RON,RUB,RWF,WST,SAR,RSD,SCR,SLL,SGD,STN,SBD,SOS,ZAR,KRW,LKR,SHP,SDG,SRD,SZL,SEK,CHF,SYP,TWD,TJS,TZS,THB,TOP,TTD,TND,TRY,TMT,AED,UGX,UAH,USD,UZS,VUV,VND,YER,CNY,ZMW,ZWL" } +google_pay = { country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" } +apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US" } [pm_filters.zen] boleto = { country = "BR", currency = "BRL" } @@ -350,6 +395,10 @@ red_pagos = { country = "UY", currency = "UYU" } [pm_filters.zsl] local_bank_transfer = { country = "CN", currency = "CNY" } + +[pm_filters.fiuu] +duit_now = { country ="MY", currency = "MYR" } + [payout_method_filters.adyenplatform] sepa = { country = "ES,SK,AT,NL,DE,BE,FR,FI,PT,IE,EE,LT,LV,IT,CZ,DE,HU,NO,PL,SE,GB,CH" , currency = "EUR,CZK,DKK,HUF,NOK,PLN,SEK,GBP,CHF" } @@ -367,6 +416,7 @@ nmi.payment_method = "card" payme.payment_method = "card" deutschebank = { payment_method = "bank_debit" } paybox = { payment_method = "card" } +nexixpay = { payment_method = "card" } #tokenization configuration which describe token lifetime and payment method for specific connector [tokenization] @@ -387,7 +437,7 @@ outgoing_enabled = true connectors_with_webhook_source_verification_call = "paypal" # List of connectors which has additional source verification api-call [unmasked_headers] -keys = "accept-language,user-agent" +keys = "accept-language,user-agent,x-profile-id" [saved_payment_methods] sdk_eligible_payment_methods = "card" @@ -400,3 +450,6 @@ card_networks = "Visa, AmericanExpress, Mastercard" [network_tokenization_supported_connectors] connector_list = "cybersource" + +[platform] +enabled = false diff --git a/config/development.toml b/config/development.toml index d723e09ec4bb..4c9b8516b5ad 100644 --- a/config/development.toml +++ b/config/development.toml @@ -99,6 +99,7 @@ cards = [ "adyen", "adyenplatform", "airwallex", + "amazonpay", "authorizedotnet", "bambora", "bamboraapac", @@ -114,9 +115,11 @@ cards = [ "cybersource", "datatrans", "deutschebank", + "digitalvirgo", "dlocal", "dummyconnector", "ebanx", + "elavon", "fiserv", "fiservemea", "fiuu", @@ -127,13 +130,16 @@ cards = [ "gpayments", "helcim", "iatapay", + "inespay", "itaubank", + "jpmorgan", "mollie", "multisafepay", "netcetera", "nexinets", "nexixpay", "nmi", + "nomupay", "noon", "novalnet", "nuvei", @@ -149,6 +155,7 @@ cards = [ "plaid", "powertranz", "prophetpay", + "redsys", "shift4", "square", "stax", @@ -158,12 +165,14 @@ cards = [ "thunes", "trustpay", "tsys", + "unified_authentication_service", "volt", "wellsfargo", "wellsfargopayout", "wise", "worldline", "worldpay", + "xendit", "zen", "zsl", ] @@ -192,6 +201,7 @@ adyenplatform.base_url = "https://balanceplatform-api-test.adyen.com/" adyen.payout_base_url = "https://pal-test.adyen.com/" adyen.dispute_base_url = "https://ca-test.adyen.com/" airwallex.base_url = "https://api-demo.airwallex.com/" +amazonpay.base_url = "https://pay-api.amazon.com/v2" applepay.base_url = "https://apple-pay-gateway.apple.com/" authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api" bambora.base_url = "https://api.na.bambora.com" @@ -211,9 +221,11 @@ cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" deutschebank.base_url = "https://testmerch.directpos.de/rest-api" +digitalvirgo.base_url = "https://dcb-integration-service-sandbox-external.staging.digitalvirgo.pl" dlocal.base_url = "https://sandbox.dlocal.com/" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" +elavon.base_url = "https://api.demo.convergepay.com/VirtualMerchantDemo/" fiserv.base_url = "https://cert.api.fiservapps.com/" fiservemea.base_url = "https://prod.emea.api.fiservapps.com/sandbox" fiuu.base_url = "https://sandbox.merchant.razer.com/" @@ -226,7 +238,10 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" +inespay.base_url = "https://apiflow.inespay.com/san/v21" itaubank.base_url = "https://sandbox.devportal.itau.com.br/" +jpmorgan.base_url = "https://api-mock.payments.jpmorgan.com/api/v2" +jpmorgan.secondary_base_url= "https://id.payments.jpmorgan.com" klarna.base_url = "https://api{{klarna_region}}.playground.klarna.com/" mifinity.base_url = "https://demo.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" @@ -236,6 +251,7 @@ netcetera.base_url = "https://{{merchant_endpoint_prefix}}.3ds-server.prev.netce nexinets.base_url = "https://apitest.payengine.de/v1" nexixpay.base_url = "https://xpaysandbox.nexigroup.com/api/phoenix-0.0/psp/api/v1" nmi.base_url = "https://secure.nmi.com/" +nomupay.base_url = "https://payout-api.sandbox.nomupay.com" noon.base_url = "https://api-test.noonpayments.com/" novalnet.base_url = "https://payport.novalnet.de/v2" noon.key_mode = "Test" @@ -255,6 +271,7 @@ powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" razorpay.base_url = "https://sandbox.juspay.in/" +redsys.base_url = "https://sis-t.redsys.es:25443/sis/realizarPago" riskified.base_url = "https://sandbox.riskified.com/api" shift4.base_url = "https://api.shift4.com/" signifyd.base_url = "https://api.signifyd.com/" @@ -269,8 +286,10 @@ stripe.base_url_file_upload = "https://files.stripe.com/" wise.base_url = "https://api.sandbox.transferwise.tech/" worldline.base_url = "https://eu.sandbox.api-ingenico.com/" worldpay.base_url = "https://try.access.worldpay.com/" +xendit.base_url = "https://api.xendit.co" trustpay.base_url = "https://test-tpgw.trustpay.eu/" tsys.base_url = "https://stagegw.transnox.com/" +unified_authentication_service.base_url = "http://localhost:8000" volt.base_url = "https://api.sandbox.volt.io/" wellsfargo.base_url = "https://apitest.cybersource.com/" wellsfargopayout.base_url = "https://api-sandbox.wellsfargo.com/" @@ -301,7 +320,7 @@ wildcard_origin = true sender_email = "example@example.com" aws_region = "" allowed_unverified_days = 1 -active_email_client = "SES" +active_email_client = "NO_EMAIL_CLIENT" recon_recipient_email = "recon@example.com" prod_intent_recipient_email = "business@example.com" @@ -314,6 +333,8 @@ password_validity_in_days = 90 two_factor_auth_expiry_in_secs = 300 totp_issuer_name = "Hyperswitch Dev" base_url = "http://localhost:8080" +force_two_factor_auth = false +force_cookies = true [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" } @@ -381,7 +402,7 @@ open_banking_pis = {currency = "EUR,GBP"} [pm_filters.adyen] google_pay = { country = "AU,NZ,JP,HK,SG,MY,TH,VN,BH,AE,KW,BR,ES,GB,SE,NO,SK,AT,NL,DE,HU,CY,LU,CH,BE,FR,DK,RO,HR,LI,MT,SI,GR,PT,IE,CZ,EE,LT,LV,IT,PL,TR,IS,CA,US", currency = "AED,ALL,AMD,ANG,AOA,ARS,AUD,AWG,AZN,BAM,BBD,BDT,BGN,BHD,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CHF,CLP,CNY,COP,CRC,CUP,CVE,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,FKP,GBP,GEL,GHS,GIP,GMD,GNF,GTQ,GYD,HKD,HNL,HTG,HUF,IDR,ILS,INR,IQD,JMD,JOD,JPY,KES,KGS,KHR,KMF,KRW,KWD,KYD,KZT,LAK,LBP,LKR,LYD,MAD,MDL,MKD,MMK,MNT,MOP,MRU,MUR,MVR,MWK,MXN,MYR,MZN,NAD,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLE,SOS,SRD,STN,SVC,SZL,THB,TND,TOP,TRY,TTD,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,XOF,XPF,YER,ZAR,ZMW" } -apple_pay = { country = "AU,NZ,CN,JP,HK,SG,MY,BH,AE,KW,BR,ES,GB,SE,NO,AT,NL,DE,HU,CY,LU,CH,BE,FR,DK,FI,RO,HR,LI,UA,MT,SI,GR,PT,IE,CZ,EE,LT,LV,IT,PL,IS,CA,US", currency = "AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD" } +apple_pay = { country = "AU,NZ,CN,JP,HK,SG,MY,BH,AE,KW,BR,ES,GB,SE,NO,AT,NL,DE,HU,CY,LU,CH,BE,FR,DK,FI,RO,HR,LI,UA,MT,SI,GR,PT,IE,CZ,EE,LT,LV,IT,PL,IS,CA,US", currency = "AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD,MYR" } paypal = { country = "AU,NZ,CN,JP,HK,MY,TH,KR,PH,ID,AE,KW,BR,ES,GB,SE,NO,SK,AT,NL,DE,HU,CY,LU,CH,BE,FR,DK,FI,RO,HR,UA,MT,SI,GI,PT,IE,CZ,EE,LT,LV,IT,PL,IS,CA,US", currency = "AUD,BRL,CAD,CZK,DKK,EUR,HKD,HUF,INR,JPY,MYR,MXN,NZD,NOK,PHP,PLN,RUB,GBP,SGD,SEK,CHF,THB,USD" } mobile_pay = { country = "DK,FI", currency = "DKK,SEK,NOK,EUR" } ali_pay = { country = "AU,JP,HK,SG,MY,TH,ES,GB,SE,NO,AT,NL,DE,CY,CH,BE,FR,DK,FI,RO,MT,SI,GR,PT,IE,IT,CA,US", currency = "USD,EUR,GBP,JPY,AUD,SGD,CHF,SEK,NOK,NZD,THB,HKD,CAD" } @@ -438,6 +459,10 @@ pay_easy = { country = "JP", currency = "JPY" } pix = { country = "BR", currency = "BRL" } boleto = { country = "BR", currency = "BRL" } +[pm_filters.bambora] +credit = { country = "US,CA", currency = "USD" } +debit = { country = "US,CA", currency = "USD" } + [pm_filters.bankofamerica] credit = { currency = "USD" } debit = { currency = "USD" } @@ -446,11 +471,23 @@ google_pay = { currency = "USD" } [pm_filters.cybersource] -credit = { currency = "USD,GBP,EUR" } -debit = { currency = "USD,GBP,EUR" } -apple_pay = { currency = "USD,GBP,EUR" } -google_pay = { currency = "USD,GBP,EUR" } +credit = { currency = "USD,GBP,EUR,PLN" } +debit = { currency = "USD,GBP,EUR,PLN" } +apple_pay = { currency = "USD,GBP,EUR,PLN" } +google_pay = { currency = "USD,GBP,EUR,PLN" } samsung_pay = { currency = "USD,GBP,EUR" } +paze = { currency = "USD" } + +[pm_filters.nexixpay] +credit = { country = "AT,BE,CY,EE,FI,FR,DE,GR,IE,IT,LV,LT,LU,MT,NL,PT,SK,SI,ES,BG,HR,DK,GB,NO,PL,CZ,RO,SE,CH,HU", currency = "ARS,AUD,BHD,CAD,CLP,CNY,COP,HRK,CZK,DKK,HKD,HUF,INR,JPY,KZT,JOD,KRW,KWD,MYR,MXN,NGN,NOK,PHP,QAR,RUB,SAR,SGD,VND,ZAR,SEK,CHF,THB,AED,EGP,GBP,USD,TWD,BYN,RSD,AZN,RON,TRY,AOA,BGN,EUR,UAH,PLN,BRL" } +debit = { country = "AT,BE,CY,EE,FI,FR,DE,GR,IE,IT,LV,LT,LU,MT,NL,PT,SK,SI,ES,BG,HR,DK,GB,NO,PL,CZ,RO,SE,CH,HU", currency = "ARS,AUD,BHD,CAD,CLP,CNY,COP,HRK,CZK,DKK,HKD,HUF,INR,JPY,KZT,JOD,KRW,KWD,MYR,MXN,NGN,NOK,PHP,QAR,RUB,SAR,SGD,VND,ZAR,SEK,CHF,THB,AED,EGP,GBP,USD,TWD,BYN,RSD,AZN,RON,TRY,AOA,BGN,EUR,UAH,PLN,BRL" } + +[pm_filters.novalnet] +credit = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} +debit = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} +apple_pay = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} +google_pay = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} +paypal = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} [pm_filters.braintree] paypal = { currency = "AUD,BRL,CAD,CNY,CZK,DKK,EUR,HKD,HUF,ILS,JPY,MYR,MXN,TWD,NZD,NOK,PHP,PLN,GBP,RUB,SGD,SEK,CHF,THB,USD" } @@ -459,6 +496,10 @@ paypal = { currency = "AUD,BRL,CAD,CNY,CZK,DKK,EUR,HKD,HUF,ILS,JPY,MYR,MXN,TWD,N credit = { currency = "USD" } debit = { currency = "USD" } +[pm.filters.jpmorgan] +debit = { country = "CA, EU, UK, US", currency = "CAD, EUR, GBP, USD" } +credit = { country = "CA, EU, UK, US", currency = "CAD, EUR, GBP, USD" } + [pm_filters.klarna] klarna = { country = "AU,AT,BE,CA,CZ,DK,FI,FR,DE,GR,IE,IT,NL,NZ,NO,PL,PT,ES,SE,CH,GB,US", currency = "AUD,EUR,EUR,CAD,CZK,DKK,EUR,EUR,EUR,EUR,EUR,EUR,EUR,NZD,NOK,PLN,EUR,EUR,SEK,CHF,GBP,USD" } @@ -511,6 +552,8 @@ google_pay = { currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" } paypal = { currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" } [pm_filters.worldpay] +debit = { country = "AF,DZ,AW,AU,AZ,BS,BH,BD,BB,BZ,BM,BT,BO,BA,BW,BR,BN,BG,BI,KH,CA,CV,KY,CL,CO,KM,CD,CR,CZ,DK,DJ,ST,DO,EC,EG,SV,ER,ET,FK,FJ,GM,GE,GH,GI,GT,GN,GY,HT,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IL,IT,JM,JP,JO,KZ,KE,KW,LA,LB,LS,LR,LY,LT,MO,MK,MG,MW,MY,MV,MR,MU,MX,MD,MN,MA,MZ,MM,NA,NZ,NI,NG,KP,NO,AR,PK,PG,PY,PE,UY,PH,PL,GB,QA,OM,RO,RU,RW,WS,SG,ST,ZA,KR,LK,SH,SD,SR,SZ,SE,CH,SY,TW,TJ,TZ,TH,TT,TN,TR,UG,UA,US,UZ,VU,VE,VN,ZM,ZW", currency = "AFN,DZD,ANG,AWG,AUD,AZN,BSD,BHD,BDT,BBD,BZD,BMD,BTN,BOB,BAM,BWP,BRL,BND,BGN,BIF,KHR,CAD,CVE,KYD,XOF,XAF,XPF,CLP,COP,KMF,CDF,CRC,EUR,CZK,DKK,DJF,DOP,XCD,EGP,SVC,ERN,ETB,EUR,FKP,FJD,GMD,GEL,GHS,GIP,GTQ,GNF,GYD,HTG,HNL,HKD,HUF,ISK,INR,IDR,IRR,IQD,ILS,JMD,JPY,JOD,KZT,KES,KWD,LAK,LBP,LSL,LRD,LYD,MOP,MKD,MGA,MWK,MYR,MVR,MRU,MUR,MXN,MDL,MNT,MAD,MZN,MMK,NAD,NPR,NZD,NIO,NGN,KPW,NOK,ARS,PKR,PAB,PGK,PYG,PEN,UYU,PHP,PLN,GBP,QAR,OMR,RON,RUB,RWF,WST,SAR,RSD,SCR,SLL,SGD,STN,SBD,SOS,ZAR,KRW,LKR,SHP,SDG,SRD,SZL,SEK,CHF,SYP,TWD,TJS,TZS,THB,TOP,TTD,TND,TRY,TMT,AED,UGX,UAH,USD,UZS,VUV,VND,YER,CNY,ZMW,ZWL" } +credit = { country = "AF,DZ,AW,AU,AZ,BS,BH,BD,BB,BZ,BM,BT,BO,BA,BW,BR,BN,BG,BI,KH,CA,CV,KY,CL,CO,KM,CD,CR,CZ,DK,DJ,ST,DO,EC,EG,SV,ER,ET,FK,FJ,GM,GE,GH,GI,GT,GN,GY,HT,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IL,IT,JM,JP,JO,KZ,KE,KW,LA,LB,LS,LR,LY,LT,MO,MK,MG,MW,MY,MV,MR,MU,MX,MD,MN,MA,MZ,MM,NA,NZ,NI,NG,KP,NO,AR,PK,PG,PY,PE,UY,PH,PL,GB,QA,OM,RO,RU,RW,WS,SG,ST,ZA,KR,LK,SH,SD,SR,SZ,SE,CH,SY,TW,TJ,TZ,TH,TT,TN,TR,UG,UA,US,UZ,VU,VE,VN,ZM,ZW", currency = "AFN,DZD,ANG,AWG,AUD,AZN,BSD,BHD,BDT,BBD,BZD,BMD,BTN,BOB,BAM,BWP,BRL,BND,BGN,BIF,KHR,CAD,CVE,KYD,XOF,XAF,XPF,CLP,COP,KMF,CDF,CRC,EUR,CZK,DKK,DJF,DOP,XCD,EGP,SVC,ERN,ETB,EUR,FKP,FJD,GMD,GEL,GHS,GIP,GTQ,GNF,GYD,HTG,HNL,HKD,HUF,ISK,INR,IDR,IRR,IQD,ILS,JMD,JPY,JOD,KZT,KES,KWD,LAK,LBP,LSL,LRD,LYD,MOP,MKD,MGA,MWK,MYR,MVR,MRU,MUR,MXN,MDL,MNT,MAD,MZN,MMK,NAD,NPR,NZD,NIO,NGN,KPW,NOK,ARS,PKR,PAB,PGK,PYG,PEN,UYU,PHP,PLN,GBP,QAR,OMR,RON,RUB,RWF,WST,SAR,RSD,SCR,SLL,SGD,STN,SBD,SOS,ZAR,KRW,LKR,SHP,SDG,SRD,SZL,SEK,CHF,SYP,TWD,TJS,TZS,THB,TOP,TTD,TND,TRY,TMT,AED,UGX,UAH,USD,UZS,VUV,VND,YER,CNY,ZMW,ZWL" } google_pay = { country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" } apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US" } @@ -522,6 +565,9 @@ region = "" credit = { currency = "USD" } debit = { currency = "USD" } +[pm_filters.fiuu] +duit_now = { country ="MY", currency = "MYR" } + [tokenization] stripe = { long_lived_token = false, payment_method = "wallet", payment_method_type = { type = "disable_only", list = "google_pay" } } checkout = { long_lived_token = false, payment_method = "wallet", apple_pay_pre_decrypt_flow = "network_tokenization" } @@ -544,6 +590,7 @@ nmi = { payment_method = "card" } payme = { payment_method = "card" } deutschebank = { payment_method = "bank_debit" } paybox = { payment_method = "card" } +nexixpay = { payment_method = "card" } [connector_customer] connector_list = "gocardless,stax,stripe" @@ -576,19 +623,31 @@ connectors_with_delayed_session_response = "trustpay,payme" connectors_with_webhook_source_verification_call = "paypal" [mandates.supported_payment_methods] -pay_later.klarna = { connector_list = "adyen" } -wallet.google_pay = { connector_list = "stripe,adyen,cybersource,bankofamerica" } -wallet.apple_pay = { connector_list = "stripe,adyen,cybersource,noon,bankofamerica" } -wallet.paypal = { connector_list = "adyen" } -card.credit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree" } -card.debit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree" } -bank_debit.ach = { connector_list = "gocardless,adyen" } -bank_debit.becs = { connector_list = "gocardless" } -bank_debit.bacs = { connector_list = "adyen" } -bank_debit.sepa = { connector_list = "gocardless,adyen" } -bank_redirect.ideal = { connector_list = "stripe,adyen,globalpay,multisafepay" } -bank_redirect.sofort = { connector_list = "stripe,adyen,globalpay" } -bank_redirect.giropay = { connector_list = "adyen,globalpay,multisafepay" } +bank_debit.ach = { connector_list = "gocardless,adyen,stripe" } +bank_debit.becs = { connector_list = "gocardless,stripe,adyen" } +bank_debit.bacs = { connector_list = "stripe,gocardless" } +bank_debit.sepa = { connector_list = "gocardless,adyen,stripe,deutschebank" } +card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" +card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" +pay_later.klarna.connector_list = "adyen" +wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon,bankofamerica,nexinets,novalnet" +wallet.google_pay.connector_list = "stripe,adyen,cybersource,bankofamerica,noon,globalpay,multisafepay,novalnet" +wallet.paypal.connector_list = "adyen,globalpay,nexinets,novalnet,paypal" +wallet.momo.connector_list = "adyen" +wallet.kakao_pay.connector_list = "adyen" +wallet.go_pay.connector_list = "adyen" +wallet.gcash.connector_list = "adyen" +wallet.dana.connector_list = "adyen" +wallet.twint.connector_list = "adyen" +wallet.vipps.connector_list = "adyen" + +bank_redirect.ideal.connector_list = "stripe,adyen,globalpay,multisafepay,nexinets" +bank_redirect.sofort.connector_list = "stripe,adyen,globalpay" +bank_redirect.giropay.connector_list = "adyen,globalpay,multisafepay,nexinets" +bank_redirect.bancontact_card.connector_list="adyen,stripe" +bank_redirect.trustly.connector_list="adyen" +bank_redirect.open_banking_uk.connector_list="adyen" +bank_redirect.eps.connector_list="globalpay,nexinets" [mandates.update_mandate_supported] card.credit = { connector_list = "cybersource" } @@ -610,6 +669,10 @@ apple_pay_ppc_key = "APPLE_PAY_PAYMENT_PROCESSING_CERTIFICATE_KEY" apple_pay_merchant_cert = "APPLE_PAY_MERCHNAT_CERTIFICATE" apple_pay_merchant_cert_key = "APPLE_PAY_MERCHNAT_CERTIFICATE_KEY" +[paze_decrypt_keys] +paze_private_key = "PAZE_PRIVATE_KEY" +paze_private_key_passphrase = "PAZE_PRIVATE_KEY_PASSPHRASE" + [generic_link] [generic_link.payment_method_collect] sdk_url = "http://localhost:9050/HyperLoader.js" @@ -677,6 +740,7 @@ authentication_analytics_topic = "hyperswitch-authentication-events" [analytics] source = "sqlx" +forex_enabled = false [analytics.clickhouse] username = "default" @@ -704,7 +768,7 @@ enabled = true file_storage_backend = "file_system" [unmasked_headers] -keys = "accept-language,user-agent" +keys = "accept-language,user-agent,x-profile-id" [opensearch] host = "https://localhost:9200" @@ -720,6 +784,10 @@ payment_attempts = "hyperswitch-payment-attempt-events" payment_intents = "hyperswitch-payment-intent-events" refunds = "hyperswitch-refund-events" disputes = "hyperswitch-dispute-events" +sessionizer_payment_attempts = "sessionizer-payment-attempt-events" +sessionizer_payment_intents = "sessionizer-payment-intent-events" +sessionizer_refunds = "sessionizer-refund-events" +sessionizer_disputes = "sessionizer-dispute-events" [saved_payment_methods] sdk_eligible_payment_methods = "card" @@ -728,8 +796,14 @@ sdk_eligible_payment_methods = "card" enabled = false global_tenant = { schema = "public", redis_key_prefix = "", clickhouse_database = "default"} -[multitenancy.tenants] -public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"} +[multitenancy.tenants.public] +base_url = "http://localhost:8080" +schema = "public" +redis_key_prefix = "" +clickhouse_database = "default" + +[multitenancy.tenants.public.user] +control_center_url = "http://localhost:9000" [user_auth_methods] encryption_key = "A8EF32E029BC3342E54BF2E172A4D7AA43E8EF9D2C3A624A9F04E2EF79DC698F" @@ -745,3 +819,21 @@ card_networks = "Visa, AmericanExpress, Mastercard" [network_tokenization_supported_connectors] connector_list = "cybersource" + +[grpc_client.dynamic_routing_client] +host = "localhost" +port = 7000 +service = "dynamo" + +[theme.storage] +file_storage_backend = "file_system" # Theme storage backend to be used + +[theme.email_config] +entity_name = "Hyperswitch" # Name of the entity to be showed in emails +entity_logo_url = "https://example.com/logo.svg" # Logo URL of the entity to be used in emails +foreground_color = "#000000" # Foreground color of email text +primary_color = "#006DF9" # Primary color of email body +background_color = "#FFFFFF" # Background color of email body + +[platform] +enabled = true diff --git a/config/docker_compose.toml b/config/docker_compose.toml index fad5759648b4..75699d0a9674 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -56,6 +56,8 @@ password_validity_in_days = 90 two_factor_auth_expiry_in_secs = 300 totp_issuer_name = "Hyperswitch" base_url = "http://localhost:8080" +force_two_factor_auth = false +force_cookies = true [locker] host = "" @@ -112,6 +114,7 @@ adyenplatform.base_url = "https://balanceplatform-api-test.adyen.com/" adyen.payout_base_url = "https://pal-test.adyen.com/" adyen.dispute_base_url = "https://ca-test.adyen.com/" airwallex.base_url = "https://api-demo.airwallex.com/" +amazonpay.base_url = "https://pay-api.amazon.com/v2" applepay.base_url = "https://apple-pay-gateway.apple.com/" authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api" bambora.base_url = "https://api.na.bambora.com" @@ -131,9 +134,11 @@ cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" deutschebank.base_url = "https://testmerch.directpos.de/rest-api" +digitalvirgo.base_url = "https://dcb-integration-service-sandbox-external.staging.digitalvirgo.pl" dlocal.base_url = "https://sandbox.dlocal.com/" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" +elavon.base_url = "https://api.demo.convergepay.com/VirtualMerchantDemo/" fiserv.base_url = "https://cert.api.fiservapps.com/" fiservemea.base_url = "https://prod.emea.api.fiservapps.com/sandbox" fiuu.base_url = "https://sandbox.merchant.razer.com/" @@ -146,7 +151,10 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" +inespay.base_url = "https://apiflow.inespay.com/san/v21" itaubank.base_url = "https://sandbox.devportal.itau.com.br/" +jpmorgan.base_url = "https://api-mock.payments.jpmorgan.com/api/v2" +jpmorgan.secondary_base_url="https://id.payments.jpmorgan.com" klarna.base_url = "https://api{{klarna_region}}.playground.klarna.com/" mifinity.base_url = "https://demo.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" @@ -156,6 +164,7 @@ netcetera.base_url = "https://{{merchant_endpoint_prefix}}.3ds-server.prev.netce nexinets.base_url = "https://apitest.payengine.de/v1" nexixpay.base_url = "https://xpaysandbox.nexigroup.com/api/phoenix-0.0/psp/api/v1" nmi.base_url = "https://secure.nmi.com/" +nomupay.base_url = "https://payout-api.sandbox.nomupay.com" noon.base_url = "https://api-test.noonpayments.com/" novalnet.base_url = "https://payport.novalnet.de/v2" noon.key_mode = "Test" @@ -175,6 +184,7 @@ powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" razorpay.base_url = "https://sandbox.juspay.in/" +redsys.base_url = "https://sis-t.redsys.es:25443/sis/realizarPago" riskified.base_url = "https://sandbox.riskified.com/api" shift4.base_url = "https://api.shift4.com/" signifyd.base_url = "https://api.signifyd.com/" @@ -189,12 +199,14 @@ stripe.base_url_file_upload = "https://files.stripe.com/" trustpay.base_url = "https://test-tpgw.trustpay.eu/" trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/" tsys.base_url = "https://stagegw.transnox.com/" +unified_authentication_service.base_url = "http://localhost:8000" volt.base_url = "https://api.sandbox.volt.io/" wellsfargo.base_url = "https://apitest.cybersource.com/" wellsfargopayout.base_url = "https://api-sandbox.wellsfargo.com/" wise.base_url = "https://api.sandbox.transferwise.tech/" worldline.base_url = "https://eu.sandbox.api-ingenico.com/" worldpay.base_url = "https://try.access.worldpay.com/" +xendit.base_url = "https://api.xendit.co" zen.base_url = "https://api.zen-test.com/" zen.secondary_base_url = "https://secure.zen-test.com/" zsl.base_url = "https://api.sitoffalb.net/" @@ -211,6 +223,7 @@ cards = [ "adyen", "adyenplatform", "airwallex", + "amazonpay", "authorizedotnet", "bambora", "bamboraapac", @@ -226,9 +239,11 @@ cards = [ "cybersource", "datatrans", "deutschebank", + "digitalvirgo", "dlocal", "dummyconnector", "ebanx", + "elavon", "fiserv", "fiservemea", "fiuu", @@ -239,13 +254,16 @@ cards = [ "gpayments", "helcim", "iatapay", + "inespay", "itaubank", + "jpmorgan", "mollie", "multisafepay", "netcetera", "nexinets", "nexixpay", "nmi", + "nomupay", "noon", "novalnet", "nuvei", @@ -261,6 +279,7 @@ cards = [ "plaid", "powertranz", "prophetpay", + "redsys", "shift4", "square", "stax", @@ -270,12 +289,14 @@ cards = [ "thunes", "trustpay", "tsys", + "unified_authentication_service", "volt", "wellsfargo", "wellsfargopayout", "wise", "worldline", "worldpay", + "xendit", "zen", "zsl", ] @@ -320,6 +341,7 @@ nmi = { payment_method = "card" } payme = { payment_method = "card" } deutschebank = { payment_method = "bank_debit" } paybox = { payment_method = "card" } +nexixpay = { payment_method = "card" } [dummy_connector] enabled = true @@ -345,35 +367,72 @@ discord_invite_url = "https://discord.gg/wJZ7DVW8mm" payout_eligibility = true [pm_filters.adyen] -online_banking_fpx = { country = "MY", currency = "MYR" } -online_banking_thailand = { country = "TH", currency = "THB" } -touch_n_go = { country = "MY", currency = "MYR" } +ach = { country = "US", currency = "USD" } +affirm = { country = "US", currency = "USD" } +afterpay_clearpay = { country = "US,CA,GB,AU,NZ", currency = "GBP,AUD,NZD,CAD,USD" } +alfamart = { country = "ID", currency = "IDR" } +ali_pay = { country = "AU,JP,HK,SG,MY,TH,ES,GB,SE,NO,AT,NL,DE,CY,CH,BE,FR,DK,FI,RO,MT,SI,GR,PT,IE,IT,CA,US", currency = "USD,EUR,GBP,JPY,AUD,SGD,CHF,SEK,NOK,NZD,THB,HKD,CAD" } +ali_pay_hk = { country = "HK", currency = "HKD" } +alma = { country = "FR", currency = "EUR" } +apple_pay = { country = "AU,NZ,CN,JP,HK,SG,MY,BH,AE,KW,BR,ES,GB,SE,NO,AT,NL,DE,HU,CY,LU,CH,BE,FR,DK,FI,RO,HR,LI,UA,MT,SI,GR,PT,IE,CZ,EE,LT,LV,IT,PL,IS,CA,US", currency = "AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD,MYR" } atome = { country = "MY,SG", currency = "MYR,SGD" } -swish = { country = "SE", currency = "SEK" } -permata_bank_transfer = { country = "ID", currency = "IDR" } +bacs = { country = "GB", currency = "GBP" } +bancontact_card = { country = "BE", currency = "EUR" } bca_bank_transfer = { country = "ID", currency = "IDR" } +bizum = { country = "ES", currency = "EUR" } +blik = { country = "PL", currency = "PLN" } bni_va = { country = "ID", currency = "IDR" } +boleto = { country = "BR", currency = "BRL" } bri_va = { country = "ID", currency = "IDR" } cimb_va = { country = "ID", currency = "IDR" } +dana = { country = "ID", currency = "IDR" } danamon_va = { country = "ID", currency = "IDR" } -mandiri_va = { country = "ID", currency = "IDR" } -alfamart = { country = "ID", currency = "IDR" } +eps = { country = "AT", currency = "EUR" } +family_mart = { country = "JP", currency = "JPY" } +gcash = { country = "PH", currency = "PHP" } +giropay = { country = "DE", currency = "EUR" } +go_pay = { country = "ID", currency = "IDR" } +google_pay = { country = "AU,NZ,JP,HK,SG,MY,TH,VN,BH,AE,KW,BR,ES,GB,SE,NO,SK,AT,NL,DE,HU,CY,LU,CH,BE,FR,DK,RO,HR,LI,MT,SI,GR,PT,IE,CZ,EE,LT,LV,IT,PL,TR,IS,CA,US", currency = "AED,ALL,AMD,ANG,AOA,ARS,AUD,AWG,AZN,BAM,BBD,BDT,BGN,BHD,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CHF,CLP,CNY,COP,CRC,CUP,CVE,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,FKP,GBP,GEL,GHS,GIP,GMD,GNF,GTQ,GYD,HKD,HNL,HTG,HUF,IDR,ILS,INR,IQD,JMD,JOD,JPY,KES,KGS,KHR,KMF,KRW,KWD,KYD,KZT,LAK,LBP,LKR,LYD,MAD,MDL,MKD,MMK,MNT,MOP,MRU,MUR,MVR,MWK,MXN,MYR,MZN,NAD,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLE,SOS,SRD,STN,SVC,SZL,THB,TND,TOP,TRY,TTD,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,XOF,XPF,YER,ZAR,ZMW" } +ideal = { country = "NL", currency = "EUR" } indomaret = { country = "ID", currency = "IDR" } +kakao_pay = { country = "KR", currency = "KRW" } +klarna = { country = "AU,AT,BE,CA,CZ,DK,FI,FR,DE,GR,IE,IT,NO,PL,PT,RO,ES,SE,CH,NL,GB,US", currency = "AUD,EUR,CAD,CZK,DKK,NOK,PLN,RON,SEK,CHF,GBP,USD"} +lawson = { country = "JP", currency = "JPY" } +mandiri_va = { country = "ID", currency = "IDR" } +mb_way = { country = "PT", currency = "EUR" } +mini_stop = { country = "JP", currency = "JPY" } +mobile_pay = { country = "DK,FI", currency = "DKK,SEK,NOK,EUR" } +momo = { country = "VN", currency = "VND" } +momo_atm = { country = "VN", currency = "VND" } +online_banking_czech_republic = { country = "CZ", currency = "EUR,CZK" } +online_banking_finland = { country = "FI", currency = "EUR" } +online_banking_fpx = { country = "MY", currency = "MYR" } +online_banking_poland = { country = "PL", currency = "PLN" } +online_banking_slovakia = { country = "SK", currency = "EUR,CZK" } +online_banking_thailand = { country = "TH", currency = "THB" } open_banking_uk = { country = "GB", currency = "GBP" } oxxo = { country = "MX", currency = "MXN" } +pay_bright = { country = "CA", currency = "CAD" } +pay_easy = { country = "JP", currency = "JPY" } pay_safe_card = { country = "AT,AU,BE,BR,BE,CA,HR,CY,CZ,DK,FI,FR,GE,DE,GI,HU,IS,IE,KW,LV,IE,LI,LT,LU,MT,MX,MD,ME,NL,NZ,NO,PY,PE,PL,PT,RO,SA,RS,SK,SI,ES,SE,CH,TR,AE,GB,US,UY", currency = "EUR,AUD,BRL,CAD,CZK,DKK,GEL,GIP,HUF,KWD,CHF,MXN,MDL,NZD,NOK,PYG,PEN,PLN,RON,SAR,RSD,SEK,TRY,AED,GBP,USD,UYU" } -seven_eleven = { country = "JP", currency = "JPY" } -lawson = { country = "JP", currency = "JPY" } -mini_stop = { country = "JP", currency = "JPY" } -family_mart = { country = "JP", currency = "JPY" } +permata_bank_transfer = { country = "ID", currency = "IDR" } seicomart = { country = "JP", currency = "JPY" } -pay_easy = { country = "JP", currency = "JPY" } -boleto = { country = "BR", currency = "BRL" } -ideal = { country = "NL", currency = "EUR" } -klarna = { country = "AU,AT,BE,CA,CZ,DK,FI,FR,DE,GR,IE,IT,NO,PL,PT,RO,ES,SE,CH,NL,GB,US", currency = "AUD,EUR,CAD,CZK,DKK,NOK,PLN,RON,SEK,CHF,GBP,USD" } +sepa = { country = "ES,SK,AT,NL,DE,BE,FR,FI,PT,IE,EE,LT,LV,IT", currency = "EUR" } +seven_eleven = { country = "JP", currency = "JPY" } +sofort = { country = "AT,BE,DE,ES,CH,NL", currency = "CHF,EUR"} paypal = { country = "AU,NZ,CN,JP,HK,MY,TH,KR,PH,ID,AE,KW,BR,ES,GB,SE,NO,SK,AT,NL,DE,HU,CY,LU,CH,BE,FR,DK,FI,RO,HR,UA,MT,SI,GI,PT,IE,CZ,EE,LT,LV,IT,PL,IS,CA,US", currency = "AUD,BRL,CAD,CZK,DKK,EUR,HKD,HUF,INR,JPY,MYR,MXN,NZD,NOK,PHP,PLN,RUB,GBP,SGD,SEK,CHF,THB,USD" } -sofort = { country = "AT,BE,DE,ES,CH,NL", currency = "CHF,EUR" } +swish = { country = "SE", currency = "SEK" } +touch_n_go = { country = "MY", currency = "MYR" } +trustly = { country = "ES,GB,SE,NO,AT,NL,DE,DK,FI,EE,LT,LV", currency = "CZK,DKK,EUR,GBP,NOK,SEK" } +twint = { country = "CH", currency = "CHF" } +vipps = { country = "NO", currency = "NOK" } +walley = { country = "SE,NO,DK,FI", currency = "DKK,EUR,NOK,SEK" } +we_chat_pay = { country = "AU,NZ,CN,JP,HK,SG,ES,GB,SE,NO,AT,NL,DE,CY,CH,BE,FR,DK,LI,MT,SI,GR,PT,IT,CA,US", currency = "AUD,CAD,CNY,EUR,GBP,HKD,JPY,NZD,SGD,USD,CNY" } + +[pm_filters.bambora] +credit = { country = "US,CA", currency = "USD" } +debit = { country = "US,CA", currency = "USD" } [pm_filters.volt] open_banking_uk = { country = "DE,GB,AT,BE,CY,EE,ES,FI,FR,GR,HR,IE,IT,LT,LU,LV,MT,NL,PT,SI,SK,BG,CZ,DK,HU,NO,PL,RO,SE,AU,BR", currency = "EUR,GBP,DKK,NOK,PLN,SEK,AUD,BRL" } @@ -412,11 +471,23 @@ apple_pay = { currency = "USD" } google_pay = { currency = "USD" } [pm_filters.cybersource] -credit = { currency = "USD,GBP,EUR" } -debit = { currency = "USD,GBP,EUR" } -apple_pay = { currency = "USD,GBP,EUR" } -google_pay = { currency = "USD,GBP,EUR" } +credit = { currency = "USD,GBP,EUR,PLN" } +debit = { currency = "USD,GBP,EUR,PLN" } +apple_pay = { currency = "USD,GBP,EUR,PLN" } +google_pay = { currency = "USD,GBP,EUR,PLN" } samsung_pay = { currency = "USD,GBP,EUR" } +paze = { currency = "USD" } + +[pm_filters.nexixpay] +credit = { country = "AT,BE,CY,EE,FI,FR,DE,GR,IE,IT,LV,LT,LU,MT,NL,PT,SK,SI,ES,BG,HR,DK,GB,NO,PL,CZ,RO,SE,CH,HU", currency = "ARS,AUD,BHD,CAD,CLP,CNY,COP,HRK,CZK,DKK,HKD,HUF,INR,JPY,KZT,JOD,KRW,KWD,MYR,MXN,NGN,NOK,PHP,QAR,RUB,SAR,SGD,VND,ZAR,SEK,CHF,THB,AED,EGP,GBP,USD,TWD,BYN,RSD,AZN,RON,TRY,AOA,BGN,EUR,UAH,PLN,BRL" } +debit = { country = "AT,BE,CY,EE,FI,FR,DE,GR,IE,IT,LV,LT,LU,MT,NL,PT,SK,SI,ES,BG,HR,DK,GB,NO,PL,CZ,RO,SE,CH,HU", currency = "ARS,AUD,BHD,CAD,CLP,CNY,COP,HRK,CZK,DKK,HKD,HUF,INR,JPY,KZT,JOD,KRW,KWD,MYR,MXN,NGN,NOK,PHP,QAR,RUB,SAR,SGD,VND,ZAR,SEK,CHF,THB,AED,EGP,GBP,USD,TWD,BYN,RSD,AZN,RON,TRY,AOA,BGN,EUR,UAH,PLN,BRL" } + +[pm_filters.novalnet] +credit = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} +debit = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} +apple_pay = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} +google_pay = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} +paypal = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,BS,BW,BY,BZ,CA,CD,CH,CL,CN,CO,CR,CU,CY,CZ,DE,DJ,DK,DO,DZ,EE,EG,ET,ES,FI,FJ,FR,GB,GE,GH,GI,GM,GR,GT,GY,HK,HN,HR,HU,ID,IE,IL,IN,IS,IT,JM,JO,JP,KE,KH,KR,KW,KY,KZ,LB,LK,LT,LV,LY,MA,MC,MD,ME,MG,MK,MN,MO,MT,MV,MW,MX,MY,NG,NI,NO,NP,NL,NZ,OM,PA,PE,PG,PH,PK,PL,PT,PY,QA,RO,RS,RU,RW,SA,SB,SC,SE,SG,SH,SI,SK,SL,SO,SM,SR,ST,SV,SY,TH,TJ,TN,TO,TR,TW,TZ,UA,UG,US,UY,UZ,VE,VA,VN,VU,WS,CF,AG,DM,GD,KN,LC,VC,YE,ZA,ZM", currency = "AED,ALL,AMD,ARS,AUD,AZN,BAM,BBD,BDT,BGN,BHD,BIF,BMD,BND,BOB,BRL,BSD,BWP,BYN,BZD,CAD,CDF,CHF,CLP,CNY,COP,CRC,CUP,CZK,DJF,DKK,DOP,DZD,EGP,ETB,EUR,FJD,GBP,GEL,GHS,GIP,GMD,GTQ,GYD,HKD,HNL,HRK,HUF,IDR,ILS,INR,ISK,JMD,JOD,JPY,KES,KHR,KRW,KWD,KYD,KZT,LBP,LKR,LYD,MAD,MDL,MGA,MKD,MNT,MOP,MVR,MWK,MXN,MYR,NGN,NIO,NOK,NPR,NZD,OMR,PAB,PEN,PGK,PHP,PKR,PLN,PYG,QAR,RON,RSD,RUB,RWF,SAR,SBD,SCR,SEK,SGD,SHP,SLL,SOS,SRD,STN,SVC,SYP,THB,TJS,TND,TOP,TRY,TWD,TZS,UAH,UGX,USD,UYU,UZS,VES,VND,VUV,WST,XAF,XCD,YER,ZAR,ZMW"} [pm_filters.helcim] credit = { currency = "USD" } @@ -433,6 +504,12 @@ credit = { currency = "USD" } debit = { currency = "USD" } ach = { currency = "USD" } +[pm_filters.worldpay] +debit = { country = "AF,DZ,AW,AU,AZ,BS,BH,BD,BB,BZ,BM,BT,BO,BA,BW,BR,BN,BG,BI,KH,CA,CV,KY,CL,CO,KM,CD,CR,CZ,DK,DJ,ST,DO,EC,EG,SV,ER,ET,FK,FJ,GM,GE,GH,GI,GT,GN,GY,HT,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IL,IT,JM,JP,JO,KZ,KE,KW,LA,LB,LS,LR,LY,LT,MO,MK,MG,MW,MY,MV,MR,MU,MX,MD,MN,MA,MZ,MM,NA,NZ,NI,NG,KP,NO,AR,PK,PG,PY,PE,UY,PH,PL,GB,QA,OM,RO,RU,RW,WS,SG,ST,ZA,KR,LK,SH,SD,SR,SZ,SE,CH,SY,TW,TJ,TZ,TH,TT,TN,TR,UG,UA,US,UZ,VU,VE,VN,ZM,ZW", currency = "AFN,DZD,ANG,AWG,AUD,AZN,BSD,BHD,BDT,BBD,BZD,BMD,BTN,BOB,BAM,BWP,BRL,BND,BGN,BIF,KHR,CAD,CVE,KYD,XOF,XAF,XPF,CLP,COP,KMF,CDF,CRC,EUR,CZK,DKK,DJF,DOP,XCD,EGP,SVC,ERN,ETB,EUR,FKP,FJD,GMD,GEL,GHS,GIP,GTQ,GNF,GYD,HTG,HNL,HKD,HUF,ISK,INR,IDR,IRR,IQD,ILS,JMD,JPY,JOD,KZT,KES,KWD,LAK,LBP,LSL,LRD,LYD,MOP,MKD,MGA,MWK,MYR,MVR,MRU,MUR,MXN,MDL,MNT,MAD,MZN,MMK,NAD,NPR,NZD,NIO,NGN,KPW,NOK,ARS,PKR,PAB,PGK,PYG,PEN,UYU,PHP,PLN,GBP,QAR,OMR,RON,RUB,RWF,WST,SAR,RSD,SCR,SLL,SGD,STN,SBD,SOS,ZAR,KRW,LKR,SHP,SDG,SRD,SZL,SEK,CHF,SYP,TWD,TJS,TZS,THB,TOP,TTD,TND,TRY,TMT,AED,UGX,UAH,USD,UZS,VUV,VND,YER,CNY,ZMW,ZWL" } +credit = { country = "AF,DZ,AW,AU,AZ,BS,BH,BD,BB,BZ,BM,BT,BO,BA,BW,BR,BN,BG,BI,KH,CA,CV,KY,CL,CO,KM,CD,CR,CZ,DK,DJ,ST,DO,EC,EG,SV,ER,ET,FK,FJ,GM,GE,GH,GI,GT,GN,GY,HT,HN,HK,HU,IS,IN,ID,IR,IQ,IE,IL,IT,JM,JP,JO,KZ,KE,KW,LA,LB,LS,LR,LY,LT,MO,MK,MG,MW,MY,MV,MR,MU,MX,MD,MN,MA,MZ,MM,NA,NZ,NI,NG,KP,NO,AR,PK,PG,PY,PE,UY,PH,PL,GB,QA,OM,RO,RU,RW,WS,SG,ST,ZA,KR,LK,SH,SD,SR,SZ,SE,CH,SY,TW,TJ,TZ,TH,TT,TN,TR,UG,UA,US,UZ,VU,VE,VN,ZM,ZW", currency = "AFN,DZD,ANG,AWG,AUD,AZN,BSD,BHD,BDT,BBD,BZD,BMD,BTN,BOB,BAM,BWP,BRL,BND,BGN,BIF,KHR,CAD,CVE,KYD,XOF,XAF,XPF,CLP,COP,KMF,CDF,CRC,EUR,CZK,DKK,DJF,DOP,XCD,EGP,SVC,ERN,ETB,EUR,FKP,FJD,GMD,GEL,GHS,GIP,GTQ,GNF,GYD,HTG,HNL,HKD,HUF,ISK,INR,IDR,IRR,IQD,ILS,JMD,JPY,JOD,KZT,KES,KWD,LAK,LBP,LSL,LRD,LYD,MOP,MKD,MGA,MWK,MYR,MVR,MRU,MUR,MXN,MDL,MNT,MAD,MZN,MMK,NAD,NPR,NZD,NIO,NGN,KPW,NOK,ARS,PKR,PAB,PGK,PYG,PEN,UYU,PHP,PLN,GBP,QAR,OMR,RON,RUB,RWF,WST,SAR,RSD,SCR,SLL,SGD,STN,SBD,SOS,ZAR,KRW,LKR,SHP,SDG,SRD,SZL,SEK,CHF,SYP,TWD,TJS,TZS,THB,TOP,TTD,TND,TRY,TMT,AED,UGX,UAH,USD,UZS,VUV,VND,YER,CNY,ZMW,ZWL" } +google_pay = { country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" } +apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US" } + [bank_config.online_banking_fpx] adyen.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" fiuu.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_of_china,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" @@ -532,7 +609,7 @@ source = "logs" file_storage_backend = "file_system" [unmasked_headers] -keys = "accept-language,user-agent" +keys = "accept-language,user-agent,x-profile-id" [opensearch] host = "https://opensearch:9200" @@ -548,6 +625,10 @@ payment_attempts = "hyperswitch-payment-attempt-events" payment_intents = "hyperswitch-payment-intent-events" refunds = "hyperswitch-refund-events" disputes = "hyperswitch-dispute-events" +sessionizer_payment_attempts = "sessionizer-payment-attempt-events" +sessionizer_payment_intents = "sessionizer-payment-intent-events" +sessionizer_refunds = "sessionizer-refund-events" +sessionizer_disputes = "sessionizer-dispute-events" [saved_payment_methods] sdk_eligible_payment_methods = "card" @@ -556,8 +637,14 @@ sdk_eligible_payment_methods = "card" enabled = false global_tenant = { schema = "public", redis_key_prefix = "", clickhouse_database = "default" } -[multitenancy.tenants] -public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default" } +[multitenancy.tenants.public] +base_url = "http://localhost:8080" +schema = "public" +redis_key_prefix = "" +clickhouse_database = "default" + +[multitenancy.tenants.public.user] +control_center_url = "http://localhost:9000" [user_auth_methods] encryption_key = "A8EF32E029BC3342E54BF2E172A4D7AA43E8EF9D2C3A624A9F04E2EF79DC698F" @@ -608,7 +695,7 @@ connector_list = "cybersource" sender_email = "example@example.com" # Sender email aws_region = "" # AWS region used by AWS SES allowed_unverified_days = 1 # Number of days the api calls ( with jwt token ) can be made without verifying the email -active_email_client = "SES" # The currently active email client +active_email_client = "NO_EMAIL_CLIENT" # The currently active email client recon_recipient_email = "recon@example.com" # Recipient email for recon request email prod_intent_recipient_email = "business@example.com" # Recipient email for prod intent email @@ -616,3 +703,16 @@ prod_intent_recipient_email = "business@example.com" # Recipient email for prod [email.aws_ses] email_role_arn = "" # The amazon resource name ( arn ) of the role which has permission to send emails sts_role_session_name = "" # An identifier for the assumed role session, used to uniquely identify a session. + +[theme.storage] +file_storage_backend = "file_system" # Theme storage backend to be used + +[theme.email_config] +entity_name = "Hyperswitch" # Name of the entity to be showed in emails +entity_logo_url = "https://example.com/logo.svg" # Logo URL of the entity to be used in emails +foreground_color = "#000000" # Foreground color of email text +primary_color = "#006DF9" # Primary color of email body +background_color = "#FFFFFF" # Background color of email body + +[platform] +enabled = true diff --git a/config/loki.yaml b/config/loki.yaml index da84e3c3e22d..51b4fa3e40da 100644 --- a/config/loki.yaml +++ b/config/loki.yaml @@ -23,9 +23,9 @@ ingester: schema_config: configs: - from: 2020-10-24 - store: boltdb-shipper + store: tsdb object_store: filesystem - schema: v11 + schema: v13 index: prefix: index_ period: 24h diff --git a/config/otel-collector.yaml b/config/otel-collector.yaml index d7d571c7c87a..9e64dbc4e229 100644 --- a/config/otel-collector.yaml +++ b/config/otel-collector.yaml @@ -2,6 +2,7 @@ receivers: otlp: protocols: grpc: + endpoint: 0.0.0.0:4317 exporters: otlp: @@ -9,8 +10,8 @@ exporters: tls: insecure: true - logging: - loglevel: debug + debug: + verbosity: detailed prometheus: endpoint: 0.0.0.0:8889 @@ -21,7 +22,7 @@ exporters: service: telemetry: logs: - level: debug + level: DEBUG metrics: level: detailed address: 0.0.0.0:8888 diff --git a/config/vector.yaml b/config/vector.yaml index 54ff25cab5af..4b801935ae5b 100644 --- a/config/vector.yaml +++ b/config/vector.yaml @@ -1,5 +1,16 @@ acknowledgements: enabled: true +enrichment_tables: + sdk_map: + type: file + file: + path: /etc/vector/config/sdk_map.csv + encoding: + type: csv + schema: + publishable_key: string + merchant_id: string + api: enabled: true @@ -18,6 +29,15 @@ sources: decoding: codec: json + sessionized_kafka_tx_events: + type: kafka + bootstrap_servers: kafka0:29092 + group_id: sessionizer + topics: + - ^sessionizer + decoding: + codec: json + app_logs: type: docker_logs include_labels: @@ -35,10 +55,19 @@ sources: encoding: json transforms: + events_create_ts: + inputs: + - kafka_tx_events + source: |- + .timestamp = from_unix_timestamp(.created_at, unit: "seconds") ?? now() + ."@timestamp" = from_unix_timestamp(.created_at, unit: "seconds") ?? now() + type: remap + plus_1_events: type: filter inputs: - - kafka_tx_events + - events_create_ts + - sessionized_events_create_ts condition: ".sign_flag == 1" hs_server_logs: @@ -54,13 +83,13 @@ transforms: source: |- .message = parse_json!(.message) - events: + sessionized_events_create_ts: type: remap inputs: - - plus_1_events + - sessionized_kafka_tx_events source: |- - .timestamp = from_unix_timestamp!(.created_at, unit: "seconds") - ."@timestamp" = from_unix_timestamp(.created_at, unit: "seconds") ?? now() + .timestamp = from_unix_timestamp(.created_at, unit: "milliseconds") ?? now() + ."@timestamp" = from_unix_timestamp(.created_at, unit: "milliseconds") ?? now() sdk_transformed: type: throttle @@ -69,12 +98,27 @@ transforms: key_field: "{{ .payment_id }}{{ .merchant_id }}" threshold: 1000 window_secs: 60 + + amend_sdk_logs: + type: remap + inputs: + - sdk_transformed + source: | + .before_transform = now() + + merchant_id = .merchant_id + row = get_enrichment_table_record!("sdk_map", { "publishable_key" : merchant_id }, case_sensitive: true) + .merchant_id = row.merchant_id + + .after_transform = now() + + sinks: opensearch_events_1: type: elasticsearch inputs: - - events + - plus_1_events endpoints: - "https://opensearch:9200" id_key: message_key @@ -92,13 +136,16 @@ sinks: - offset - partition - topic + - clickhouse_database + - last_synced + - sign_flag bulk: index: "vector-{{ .topic }}" opensearch_events_2: type: elasticsearch inputs: - - events + - plus_1_events endpoints: - "https://opensearch:9200" id_key: message_key @@ -116,10 +163,40 @@ sinks: - offset - partition - topic + - clickhouse_database + - last_synced + - sign_flag bulk: # Add a date suffixed index for better grouping index: "vector-{{ .topic }}-%Y-%m-%d" + opensearch_events_3: + type: elasticsearch + inputs: + - plus_1_events + endpoints: + - "https://opensearch:9200" + id_key: message_key + api_version: v7 + tls: + verify_certificate: false + verify_hostname: false + auth: + strategy: basic + user: admin + password: 0penS3arc# + encoding: + except_fields: + - message_key + - offset + - partition + - topic + - clickhouse_database + - last_synced + - sign_flag + bulk: + index: "{{ .topic }}" + opensearch_logs: type: elasticsearch inputs: @@ -143,6 +220,7 @@ sinks: type: loki inputs: - kafka_tx_events + - sessionized_kafka_tx_events endpoint: http://loki:3100 labels: source: vector @@ -178,7 +256,7 @@ sinks: - "path" - "source_type" inputs: - - "sdk_transformed" + - "amend_sdk_logs" bootstrap_servers: kafka0:29092 topic: hyper-sdk-logs key_field: ".merchant_id" diff --git a/connector-template/mod.rs b/connector-template/mod.rs index ae450aecf20b..ee5f4872d6eb 100644 --- a/connector-template/mod.rs +++ b/connector-template/mod.rs @@ -32,7 +32,7 @@ use hyperswitch_domain_models::{ }, }; use hyperswitch_interfaces::{ - api::{self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorValidation}, + api::{self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorValidation, ConnectorSpecifications}, configs::Connectors, errors, events::connector_api_logs::ConnectorEvent, @@ -551,3 +551,6 @@ impl webhooks::IncomingWebhook for {{project-name | downcase | pascal_case}} { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for {{project-name | downcase | pascal_case}} {} + diff --git a/connector-template/transformers.rs b/connector-template/transformers.rs index 7c81a7504655..b508596cbc08 100644 --- a/connector-template/transformers.rs +++ b/connector-template/transformers.rs @@ -75,7 +75,7 @@ impl TryFrom<&{{project-name | downcase | pascal_case}}RouterData<&PaymentsAutho card, }) } - _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), + _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), } } } @@ -132,8 +132,8 @@ impl TryFrom 0; CREATE MATERIALIZED VIEW sdk_events_audit_mv TO sdk_events_audit ( - `payment_id` Nullable(String), + `payment_id` String, `merchant_id` String, `remote_ip` Nullable(String), `log_type` LowCardinality(Nullable(String)), diff --git a/crates/analytics/src/api_event/core.rs b/crates/analytics/src/api_event/core.rs index 305de7e69c86..27cc2a4d6975 100644 --- a/crates/analytics/src/api_event/core.rs +++ b/crates/analytics/src/api_event/core.rs @@ -12,7 +12,6 @@ use common_utils::errors::ReportSwitchExt; use error_stack::ResultExt; use router_env::{ instrument, logger, - metrics::add_attributes, tracing::{self, Instrument}, }; @@ -118,7 +117,7 @@ pub async fn get_api_event_metrics( &req.group_by_names.clone(), &merchant_id_scoped, &req.filters, - &req.time_series.map(|t| t.granularity), + req.time_series.map(|t| t.granularity), &req.time_range, ) .await @@ -136,14 +135,14 @@ pub async fn get_api_event_metrics( .change_context(AnalyticsError::UnknownError)? { let data = data?; - let attributes = &add_attributes([ + let attributes = router_env::metric_attributes!( ("metric_type", metric.to_string()), ("source", pool.to_string()), - ]); + ); let value = u64::try_from(data.len()); if let Ok(val) = value { - metrics::BUCKETS_FETCHED.record(&metrics::CONTEXT, val, attributes); + metrics::BUCKETS_FETCHED.record(val, attributes); logger::debug!("Attributes: {:?}, Buckets fetched: {}", attributes, val); } for (id, value) in data { diff --git a/crates/analytics/src/api_event/metrics.rs b/crates/analytics/src/api_event/metrics.rs index ac29ec15169f..ad49f5d0ccd9 100644 --- a/crates/analytics/src/api_event/metrics.rs +++ b/crates/analytics/src/api_event/metrics.rs @@ -45,7 +45,7 @@ where dimensions: &[ApiEventDimensions], merchant_id: &common_utils::id_type::MerchantId, filters: &ApiEventFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult>; @@ -66,7 +66,7 @@ where dimensions: &[ApiEventDimensions], merchant_id: &common_utils::id_type::MerchantId, filters: &ApiEventFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { diff --git a/crates/analytics/src/api_event/metrics/api_count.rs b/crates/analytics/src/api_event/metrics/api_count.rs index f00c01bbf381..85f6cac01cec 100644 --- a/crates/analytics/src/api_event/metrics/api_count.rs +++ b/crates/analytics/src/api_event/metrics/api_count.rs @@ -32,7 +32,7 @@ where _dimensions: &[ApiEventDimensions], merchant_id: &common_utils::id_type::MerchantId, filters: &ApiEventFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -62,7 +62,7 @@ where alias: Some("end_bucket"), }) .switch()?; - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { granularity .set_group_by_clause(&mut query_builder) .attach_printable("Error adding granularity") diff --git a/crates/analytics/src/api_event/metrics/latency.rs b/crates/analytics/src/api_event/metrics/latency.rs index 5d71da2a0aa1..03c1a226e441 100644 --- a/crates/analytics/src/api_event/metrics/latency.rs +++ b/crates/analytics/src/api_event/metrics/latency.rs @@ -35,7 +35,7 @@ where _dimensions: &[ApiEventDimensions], merchant_id: &common_utils::id_type::MerchantId, filters: &ApiEventFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -67,7 +67,7 @@ where alias: Some("end_bucket"), }) .switch()?; - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { granularity .set_group_by_clause(&mut query_builder) .attach_printable("Error adding granularity") diff --git a/crates/analytics/src/api_event/metrics/status_code_count.rs b/crates/analytics/src/api_event/metrics/status_code_count.rs index b4fff367b629..f67d3d73e593 100644 --- a/crates/analytics/src/api_event/metrics/status_code_count.rs +++ b/crates/analytics/src/api_event/metrics/status_code_count.rs @@ -32,7 +32,7 @@ where _dimensions: &[ApiEventDimensions], merchant_id: &common_utils::id_type::MerchantId, filters: &ApiEventFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -68,7 +68,7 @@ where alias: Some("end_bucket"), }) .switch()?; - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { granularity .set_group_by_clause(&mut query_builder) .attach_printable("Error adding granularity") diff --git a/crates/analytics/src/auth_events/core.rs b/crates/analytics/src/auth_events/core.rs index 250dc07fee74..3fd134bc4980 100644 --- a/crates/analytics/src/auth_events/core.rs +++ b/crates/analytics/src/auth_events/core.rs @@ -38,7 +38,7 @@ pub async fn get_metrics( &metric_type, &merchant_id_scoped, &publishable_key_scoped, - &req.time_series.map(|t| t.granularity), + req.time_series.map(|t| t.granularity), &req.time_range, ) .await diff --git a/crates/analytics/src/auth_events/metrics.rs b/crates/analytics/src/auth_events/metrics.rs index da5bf6309fe4..4a1fbd0e1470 100644 --- a/crates/analytics/src/auth_events/metrics.rs +++ b/crates/analytics/src/auth_events/metrics.rs @@ -46,7 +46,7 @@ where &self, merchant_id: &common_utils::id_type::MerchantId, publishable_key: &str, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult>; @@ -66,7 +66,7 @@ where &self, merchant_id: &common_utils::id_type::MerchantId, publishable_key: &str, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { diff --git a/crates/analytics/src/auth_events/metrics/authentication_attempt_count.rs b/crates/analytics/src/auth_events/metrics/authentication_attempt_count.rs index 4fee15e2afd1..5faeefec6861 100644 --- a/crates/analytics/src/auth_events/metrics/authentication_attempt_count.rs +++ b/crates/analytics/src/auth_events/metrics/authentication_attempt_count.rs @@ -31,7 +31,7 @@ where &self, _merchant_id: &common_utils::id_type::MerchantId, publishable_key: &str, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -45,7 +45,7 @@ where }) .switch()?; - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { query_builder .add_granularity_in_mins(granularity) .switch()?; diff --git a/crates/analytics/src/auth_events/metrics/authentication_success_count.rs b/crates/analytics/src/auth_events/metrics/authentication_success_count.rs index 06de7a694fc8..663473c2d895 100644 --- a/crates/analytics/src/auth_events/metrics/authentication_success_count.rs +++ b/crates/analytics/src/auth_events/metrics/authentication_success_count.rs @@ -31,7 +31,7 @@ where &self, _merchant_id: &common_utils::id_type::MerchantId, publishable_key: &str, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -45,7 +45,7 @@ where }) .switch()?; - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { query_builder .add_granularity_in_mins(granularity) .switch()?; 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 2f744f15e909..15cd86e5cc8d 100644 --- a/crates/analytics/src/auth_events/metrics/challenge_attempt_count.rs +++ b/crates/analytics/src/auth_events/metrics/challenge_attempt_count.rs @@ -31,7 +31,7 @@ where &self, merchant_id: &common_utils::id_type::MerchantId, _publishable_key: &str, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -45,7 +45,7 @@ where }) .switch()?; - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { query_builder .add_granularity_in_mins(granularity) .switch()?; diff --git a/crates/analytics/src/auth_events/metrics/challenge_flow_count.rs b/crates/analytics/src/auth_events/metrics/challenge_flow_count.rs index 551810d8e5a7..61b10e6fb9e9 100644 --- a/crates/analytics/src/auth_events/metrics/challenge_flow_count.rs +++ b/crates/analytics/src/auth_events/metrics/challenge_flow_count.rs @@ -31,7 +31,7 @@ where &self, _merchant_id: &common_utils::id_type::MerchantId, publishable_key: &str, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -45,7 +45,7 @@ where }) .switch()?; - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { query_builder .add_granularity_in_mins(granularity) .switch()?; 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 c6c7d1cda08d..c5bf7bf81cec 100644 --- a/crates/analytics/src/auth_events/metrics/challenge_success_count.rs +++ b/crates/analytics/src/auth_events/metrics/challenge_success_count.rs @@ -31,7 +31,7 @@ where &self, merchant_id: &common_utils::id_type::MerchantId, _publishable_key: &str, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -45,7 +45,7 @@ where }) .switch()?; - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { query_builder .add_granularity_in_mins(granularity) .switch()?; diff --git a/crates/analytics/src/auth_events/metrics/frictionless_flow_count.rs b/crates/analytics/src/auth_events/metrics/frictionless_flow_count.rs index 69b4eeba4a61..c08cc511e169 100644 --- a/crates/analytics/src/auth_events/metrics/frictionless_flow_count.rs +++ b/crates/analytics/src/auth_events/metrics/frictionless_flow_count.rs @@ -31,7 +31,7 @@ where &self, _merchant_id: &common_utils::id_type::MerchantId, publishable_key: &str, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -45,7 +45,7 @@ where }) .switch()?; - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { query_builder .add_granularity_in_mins(granularity) .switch()?; diff --git a/crates/analytics/src/auth_events/metrics/frictionless_success_count.rs b/crates/analytics/src/auth_events/metrics/frictionless_success_count.rs index 6ea26a909799..b310567c9db3 100644 --- a/crates/analytics/src/auth_events/metrics/frictionless_success_count.rs +++ b/crates/analytics/src/auth_events/metrics/frictionless_success_count.rs @@ -31,7 +31,7 @@ where &self, merchant_id: &common_utils::id_type::MerchantId, _publishable_key: &str, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -45,7 +45,7 @@ where }) .switch()?; - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { query_builder .add_granularity_in_mins(granularity) .switch()?; diff --git a/crates/analytics/src/auth_events/metrics/three_ds_sdk_count.rs b/crates/analytics/src/auth_events/metrics/three_ds_sdk_count.rs index ca67400a9b2e..4ce10c9aad37 100644 --- a/crates/analytics/src/auth_events/metrics/three_ds_sdk_count.rs +++ b/crates/analytics/src/auth_events/metrics/three_ds_sdk_count.rs @@ -31,7 +31,7 @@ where &self, _merchant_id: &common_utils::id_type::MerchantId, publishable_key: &str, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -45,7 +45,7 @@ where }) .switch()?; - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { query_builder .add_granularity_in_mins(granularity) .switch()?; diff --git a/crates/analytics/src/clickhouse.rs b/crates/analytics/src/clickhouse.rs index 37a011c9a5a6..cd870c12b236 100644 --- a/crates/analytics/src/clickhouse.rs +++ b/crates/analytics/src/clickhouse.rs @@ -16,7 +16,9 @@ use super::{ distribution::PaymentDistributionRow, filters::PaymentFilterRow, metrics::PaymentMetricRow, }, query::{Aggregate, ToSql, Window}, - refunds::{filters::RefundFilterRow, metrics::RefundMetricRow}, + refunds::{ + distribution::RefundDistributionRow, filters::RefundFilterRow, metrics::RefundMetricRow, + }, sdk_events::{filters::SdkEventFilter, metrics::SdkEventMetricRow}, types::{AnalyticsCollection, AnalyticsDataSource, LoadRow, QueryExecutionError}, }; @@ -130,12 +132,18 @@ impl AnalyticsDataSource for ClickhouseClient { fn get_table_engine(table: AnalyticsCollection) -> TableEngine { match table { AnalyticsCollection::Payment + | AnalyticsCollection::PaymentSessionized | AnalyticsCollection::Refund + | AnalyticsCollection::RefundSessionized | AnalyticsCollection::FraudCheck | AnalyticsCollection::PaymentIntent + | AnalyticsCollection::PaymentIntentSessionized | AnalyticsCollection::Dispute => { TableEngine::CollapsingMergeTree { sign: "sign_flag" } } + AnalyticsCollection::DisputeSessionized => { + TableEngine::CollapsingMergeTree { sign: "sign_flag" } + } AnalyticsCollection::SdkEvents | AnalyticsCollection::SdkEventsAnalytics | AnalyticsCollection::ApiEvents @@ -164,6 +172,7 @@ impl super::payment_intents::filters::PaymentIntentFilterAnalytics for Clickhous impl super::payment_intents::metrics::PaymentIntentMetricAnalytics for ClickhouseClient {} impl super::refunds::metrics::RefundMetricAnalytics for ClickhouseClient {} impl super::refunds::filters::RefundFilterAnalytics for ClickhouseClient {} +impl super::refunds::distribution::RefundDistributionAnalytics for ClickhouseClient {} impl super::frm::metrics::FrmMetricAnalytics for ClickhouseClient {} impl super::frm::filters::FrmFilterAnalytics for ClickhouseClient {} impl super::sdk_events::filters::SdkEventFilterAnalytics for ClickhouseClient {} @@ -294,6 +303,16 @@ impl TryInto for serde_json::Value { } } +impl TryInto for serde_json::Value { + type Error = Report; + + fn try_into(self) -> Result { + serde_json::from_value(self).change_context(ParsingError::StructParseFailure( + "Failed to parse RefundDistributionRow in clickhouse results", + )) + } +} + impl TryInto for serde_json::Value { type Error = Report; @@ -423,16 +442,20 @@ impl ToSql for AnalyticsCollection { fn to_sql(&self, _table_engine: &TableEngine) -> error_stack::Result { match self { Self::Payment => Ok("payment_attempts".to_string()), + Self::PaymentSessionized => Ok("sessionizer_payment_attempts".to_string()), Self::Refund => Ok("refunds".to_string()), + Self::RefundSessionized => Ok("sessionizer_refunds".to_string()), Self::FraudCheck => Ok("fraud_check".to_string()), Self::SdkEvents => Ok("sdk_events_audit".to_string()), Self::SdkEventsAnalytics => Ok("sdk_events".to_string()), Self::ApiEvents => Ok("api_events_audit".to_string()), Self::ApiEventsAnalytics => Ok("api_events".to_string()), Self::PaymentIntent => Ok("payment_intents".to_string()), + Self::PaymentIntentSessionized => Ok("sessionizer_payment_intents".to_string()), Self::ConnectorEvents => Ok("connector_events_audit".to_string()), Self::OutgoingWebhookEvent => Ok("outgoing_webhook_events_audit".to_string()), Self::Dispute => Ok("dispute".to_string()), + Self::DisputeSessionized => Ok("sessionizer_dispute".to_string()), Self::ActivePaymentsAnalytics => Ok("active_payments".to_string()), } } diff --git a/crates/analytics/src/disputes/accumulators.rs b/crates/analytics/src/disputes/accumulators.rs index 1997d75d3230..41bd3beebdb7 100644 --- a/crates/analytics/src/disputes/accumulators.rs +++ b/crates/analytics/src/disputes/accumulators.rs @@ -5,8 +5,8 @@ use super::metrics::DisputeMetricRow; #[derive(Debug, Default)] pub struct DisputeMetricsAccumulator { pub disputes_status_rate: RateAccumulator, - pub total_amount_disputed: SumAccumulator, - pub total_dispute_lost_amount: SumAccumulator, + pub disputed_amount: DisputedAmountAccumulator, + pub dispute_lost_amount: DisputedAmountAccumulator, } #[derive(Debug, Default)] pub struct RateAccumulator { @@ -17,7 +17,7 @@ pub struct RateAccumulator { } #[derive(Debug, Default)] #[repr(transparent)] -pub struct SumAccumulator { +pub struct DisputedAmountAccumulator { pub total: Option, } @@ -29,7 +29,7 @@ pub trait DisputeMetricAccumulator { fn collect(self) -> Self::MetricOutput; } -impl DisputeMetricAccumulator for SumAccumulator { +impl DisputeMetricAccumulator for DisputedAmountAccumulator { type MetricOutput = Option; #[inline] fn add_metrics_bucket(&mut self, metrics: &DisputeMetricRow) { @@ -92,8 +92,8 @@ impl DisputeMetricsAccumulator { disputes_challenged: challenge_rate, disputes_won: won_rate, disputes_lost: lost_rate, - total_amount_disputed: self.total_amount_disputed.collect(), - total_dispute_lost_amount: self.total_dispute_lost_amount.collect(), + disputed_amount: self.disputed_amount.collect(), + dispute_lost_amount: self.dispute_lost_amount.collect(), total_dispute, } } diff --git a/crates/analytics/src/disputes/core.rs b/crates/analytics/src/disputes/core.rs index b8b44a757dec..540a14104c1f 100644 --- a/crates/analytics/src/disputes/core.rs +++ b/crates/analytics/src/disputes/core.rs @@ -5,13 +5,12 @@ use api_models::analytics::{ DisputeDimensions, DisputeMetrics, DisputeMetricsBucketIdentifier, DisputeMetricsBucketResponse, }, - AnalyticsMetadata, DisputeFilterValue, DisputeFiltersResponse, GetDisputeFilterRequest, - GetDisputeMetricRequest, MetricsResponse, + DisputeFilterValue, DisputeFiltersResponse, DisputesAnalyticsMetadata, DisputesMetricsResponse, + GetDisputeFilterRequest, GetDisputeMetricRequest, }; use error_stack::ResultExt; use router_env::{ logger, - metrics::add_attributes, tracing::{self, Instrument}, }; @@ -30,7 +29,7 @@ pub async fn get_metrics( pool: &AnalyticsProvider, auth: &AuthInfo, req: GetDisputeMetricRequest, -) -> AnalyticsResult> { +) -> AnalyticsResult> { let mut metrics_accumulator: HashMap< DisputeMetricsBucketIdentifier, DisputeMetricsAccumulator, @@ -54,7 +53,7 @@ pub async fn get_metrics( &req.group_by_names.clone(), &auth_scoped, &req.filters, - &req.time_series.map(|t| t.granularity), + req.time_series.map(|t| t.granularity), &req.time_range, ) .await @@ -72,14 +71,14 @@ pub async fn get_metrics( .change_context(AnalyticsError::UnknownError)? { let data = data?; - let attributes = &add_attributes([ + let attributes = router_env::metric_attributes!( ("metric_type", metric.to_string()), ("source", pool.to_string()), - ]); + ); let value = u64::try_from(data.len()); if let Ok(val) = value { - metrics::BUCKETS_FETCHED.record(&metrics::CONTEXT, val, attributes); + metrics::BUCKETS_FETCHED.record(val, attributes); logger::debug!("Attributes: {:?}, Buckets fetched: {}", attributes, val); } @@ -87,14 +86,17 @@ pub async fn get_metrics( logger::debug!(bucket_id=?id, bucket_value=?value, "Bucket row for metric {metric}"); let metrics_builder = metrics_accumulator.entry(id).or_default(); match metric { - DisputeMetrics::DisputeStatusMetric => metrics_builder + DisputeMetrics::DisputeStatusMetric + | DisputeMetrics::SessionizedDisputeStatusMetric => metrics_builder .disputes_status_rate .add_metrics_bucket(&value), - DisputeMetrics::TotalAmountDisputed => metrics_builder - .total_amount_disputed - .add_metrics_bucket(&value), - DisputeMetrics::TotalDisputeLostAmount => metrics_builder - .total_dispute_lost_amount + DisputeMetrics::TotalAmountDisputed + | DisputeMetrics::SessionizedTotalAmountDisputed => { + metrics_builder.disputed_amount.add_metrics_bucket(&value) + } + DisputeMetrics::TotalDisputeLostAmount + | DisputeMetrics::SessionizedTotalDisputeLostAmount => metrics_builder + .dispute_lost_amount .add_metrics_bucket(&value), } } @@ -105,18 +107,31 @@ pub async fn get_metrics( metrics_accumulator ); } + let mut total_disputed_amount = 0; + let mut total_dispute_lost_amount = 0; let query_data: Vec = metrics_accumulator .into_iter() - .map(|(id, val)| DisputeMetricsBucketResponse { - values: val.collect(), - dimensions: id, + .map(|(id, val)| { + let collected_values = val.collect(); + if let Some(amount) = collected_values.disputed_amount { + total_disputed_amount += amount; + } + if let Some(amount) = collected_values.dispute_lost_amount { + total_dispute_lost_amount += amount; + } + + DisputeMetricsBucketResponse { + values: collected_values, + dimensions: id, + } }) .collect(); - Ok(MetricsResponse { + Ok(DisputesMetricsResponse { query_data, - meta_data: [AnalyticsMetadata { - current_time_range: req.time_range, + meta_data: [DisputesAnalyticsMetadata { + total_disputed_amount: Some(total_disputed_amount), + total_dispute_lost_amount: Some(total_dispute_lost_amount), }], }) } diff --git a/crates/analytics/src/disputes/metrics.rs b/crates/analytics/src/disputes/metrics.rs index dd1aa3c1bbd4..6514e5fcbe0c 100644 --- a/crates/analytics/src/disputes/metrics.rs +++ b/crates/analytics/src/disputes/metrics.rs @@ -1,4 +1,5 @@ mod dispute_status_metric; +mod sessionized_metrics; mod total_amount_disputed; mod total_dispute_lost_amount; @@ -51,7 +52,7 @@ where dimensions: &[DisputeDimensions], auth: &AuthInfo, filters: &DisputeFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult>; @@ -72,7 +73,7 @@ where dimensions: &[DisputeDimensions], auth: &AuthInfo, filters: &DisputeFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -92,6 +93,21 @@ where .load_metrics(dimensions, auth, filters, granularity, time_range, pool) .await } + Self::SessionizedTotalAmountDisputed => { + sessionized_metrics::TotalAmountDisputed::default() + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } + Self::SessionizedDisputeStatusMetric => { + sessionized_metrics::DisputeStatusMetric::default() + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } + Self::SessionizedTotalDisputeLostAmount => { + sessionized_metrics::TotalDisputeLostAmount::default() + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } } } } diff --git a/crates/analytics/src/disputes/metrics/dispute_status_metric.rs b/crates/analytics/src/disputes/metrics/dispute_status_metric.rs index bbce460e475c..ce962e284f6f 100644 --- a/crates/analytics/src/disputes/metrics/dispute_status_metric.rs +++ b/crates/analytics/src/disputes/metrics/dispute_status_metric.rs @@ -32,7 +32,7 @@ where dimensions: &[DisputeDimensions], auth: &AuthInfo, filters: &DisputeFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> @@ -80,7 +80,7 @@ where .add_group_by_clause("dispute_status") .switch()?; - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { granularity .set_group_by_clause(&mut query_builder) .switch()?; diff --git a/crates/analytics/src/disputes/metrics/sessionized_metrics.rs b/crates/analytics/src/disputes/metrics/sessionized_metrics.rs new file mode 100644 index 000000000000..4d41194634db --- /dev/null +++ b/crates/analytics/src/disputes/metrics/sessionized_metrics.rs @@ -0,0 +1,8 @@ +mod dispute_status_metric; +mod total_amount_disputed; +mod total_dispute_lost_amount; +pub(super) use dispute_status_metric::DisputeStatusMetric; +pub(super) use total_amount_disputed::TotalAmountDisputed; +pub(super) use total_dispute_lost_amount::TotalDisputeLostAmount; + +pub use super::{DisputeMetric, DisputeMetricAnalytics, DisputeMetricRow}; diff --git a/crates/analytics/src/disputes/metrics/sessionized_metrics/dispute_status_metric.rs b/crates/analytics/src/disputes/metrics/sessionized_metrics/dispute_status_metric.rs new file mode 100644 index 000000000000..9a7b05358192 --- /dev/null +++ b/crates/analytics/src/disputes/metrics/sessionized_metrics/dispute_status_metric.rs @@ -0,0 +1,120 @@ +use std::collections::HashSet; + +use api_models::analytics::{ + disputes::{DisputeDimensions, DisputeFilters, DisputeMetricsBucketIdentifier}, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::DisputeMetricRow; +use crate::{ + enums::AuthInfo, + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; +#[derive(Default)] +pub(crate) struct DisputeStatusMetric {} + +#[async_trait::async_trait] +impl super::DisputeMetric for DisputeStatusMetric +where + T: AnalyticsDataSource + super::DisputeMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[DisputeDimensions], + auth: &AuthInfo, + filters: &DisputeFilters, + granularity: Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> + where + T: AnalyticsDataSource + super::DisputeMetricAnalytics, + { + let mut query_builder = QueryBuilder::new(AnalyticsCollection::DisputeSessionized); + + for dim in dimensions { + query_builder.add_select_column(dim).switch()?; + } + + query_builder.add_select_column("dispute_status").switch()?; + + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + time_range.set_filter_clause(&mut query_builder).switch()?; + + for dim in dimensions { + query_builder.add_group_by_clause(dim).switch()?; + } + + query_builder + .add_group_by_clause("dispute_status") + .switch()?; + + if let Some(granularity) = granularity { + granularity + .set_group_by_clause(&mut query_builder) + .switch()?; + } + + query_builder + .execute_query::(pool) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(|i| { + Ok(( + DisputeMetricsBucketIdentifier::new( + i.dispute_stage.as_ref().map(|i| i.0), + i.connector.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/disputes/metrics/sessionized_metrics/total_amount_disputed.rs b/crates/analytics/src/disputes/metrics/sessionized_metrics/total_amount_disputed.rs new file mode 100644 index 000000000000..5c5eceb06196 --- /dev/null +++ b/crates/analytics/src/disputes/metrics/sessionized_metrics/total_amount_disputed.rs @@ -0,0 +1,118 @@ +use std::collections::HashSet; + +use api_models::analytics::{ + disputes::{DisputeDimensions, DisputeFilters, DisputeMetricsBucketIdentifier}, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::DisputeMetricRow; +use crate::{ + enums::AuthInfo, + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; +#[derive(Default)] +pub(crate) struct TotalAmountDisputed {} + +#[async_trait::async_trait] +impl super::DisputeMetric for TotalAmountDisputed +where + T: AnalyticsDataSource + super::DisputeMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[DisputeDimensions], + auth: &AuthInfo, + filters: &DisputeFilters, + granularity: Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> + where + T: AnalyticsDataSource + super::DisputeMetricAnalytics, + { + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::DisputeSessionized); + + for dim in dimensions { + query_builder.add_select_column(dim).switch()?; + } + + query_builder + .add_select_column(Aggregate::Sum { + field: "dispute_amount", + alias: Some("total"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder.add_group_by_clause(dim).switch()?; + } + + if let Some(granularity) = granularity { + granularity + .set_group_by_clause(&mut query_builder) + .switch()?; + } + query_builder + .add_filter_clause("dispute_status", "dispute_won") + .switch()?; + + query_builder + .execute_query::(pool) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(|i| { + Ok(( + DisputeMetricsBucketIdentifier::new( + i.dispute_stage.as_ref().map(|i| i.0), + i.connector.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, crate::query::PostProcessingError>>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/disputes/metrics/sessionized_metrics/total_dispute_lost_amount.rs b/crates/analytics/src/disputes/metrics/sessionized_metrics/total_dispute_lost_amount.rs new file mode 100644 index 000000000000..d6308b09f33b --- /dev/null +++ b/crates/analytics/src/disputes/metrics/sessionized_metrics/total_dispute_lost_amount.rs @@ -0,0 +1,119 @@ +use std::collections::HashSet; + +use api_models::analytics::{ + disputes::{DisputeDimensions, DisputeFilters, DisputeMetricsBucketIdentifier}, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::DisputeMetricRow; +use crate::{ + enums::AuthInfo, + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; +#[derive(Default)] +pub(crate) struct TotalDisputeLostAmount {} + +#[async_trait::async_trait] +impl super::DisputeMetric for TotalDisputeLostAmount +where + T: AnalyticsDataSource + super::DisputeMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[DisputeDimensions], + auth: &AuthInfo, + filters: &DisputeFilters, + granularity: Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> + where + T: AnalyticsDataSource + super::DisputeMetricAnalytics, + { + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::DisputeSessionized); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + + query_builder + .add_select_column(Aggregate::Sum { + field: "dispute_amount", + alias: Some("total"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder.add_group_by_clause(dim).switch()?; + } + + if let Some(granularity) = granularity { + granularity + .set_group_by_clause(&mut query_builder) + .switch()?; + } + + query_builder + .add_filter_clause("dispute_status", "dispute_lost") + .switch()?; + + query_builder + .execute_query::(pool) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(|i| { + Ok(( + DisputeMetricsBucketIdentifier::new( + i.dispute_stage.as_ref().map(|i| i.0), + i.connector.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, crate::query::PostProcessingError>>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/disputes/metrics/total_amount_disputed.rs b/crates/analytics/src/disputes/metrics/total_amount_disputed.rs index 5b9d0f54622f..68c7fa6d166c 100644 --- a/crates/analytics/src/disputes/metrics/total_amount_disputed.rs +++ b/crates/analytics/src/disputes/metrics/total_amount_disputed.rs @@ -32,7 +32,7 @@ where dimensions: &[DisputeDimensions], auth: &AuthInfo, filters: &DisputeFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> @@ -77,7 +77,7 @@ where query_builder.add_group_by_clause(dim).switch()?; } - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { granularity .set_group_by_clause(&mut query_builder) .switch()?; diff --git a/crates/analytics/src/disputes/metrics/total_dispute_lost_amount.rs b/crates/analytics/src/disputes/metrics/total_dispute_lost_amount.rs index e13f3a0f5305..d14d49827010 100644 --- a/crates/analytics/src/disputes/metrics/total_dispute_lost_amount.rs +++ b/crates/analytics/src/disputes/metrics/total_dispute_lost_amount.rs @@ -32,7 +32,7 @@ where dimensions: &[DisputeDimensions], auth: &AuthInfo, filters: &DisputeFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> @@ -77,7 +77,7 @@ where query_builder.add_group_by_clause(dim).switch()?; } - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { granularity .set_group_by_clause(&mut query_builder) .switch()?; diff --git a/crates/analytics/src/errors.rs b/crates/analytics/src/errors.rs index 0e39a4ddd928..d7b15a6db115 100644 --- a/crates/analytics/src/errors.rs +++ b/crates/analytics/src/errors.rs @@ -12,6 +12,8 @@ pub enum AnalyticsError { UnknownError, #[error("Access Forbidden Analytics Error")] AccessForbiddenError, + #[error("Failed to fetch currency exchange rate")] + ForexFetchFailed, } impl ErrorSwitch for AnalyticsError { @@ -32,6 +34,12 @@ impl ErrorSwitch for AnalyticsError { Self::AccessForbiddenError => { ApiErrorResponse::Unauthorized(ApiError::new("IR", 0, "Access Forbidden", None)) } + Self::ForexFetchFailed => ApiErrorResponse::InternalServerError(ApiError::new( + "HE", + 0, + "Failed to fetch currency exchange rate", + None, + )), } } } diff --git a/crates/analytics/src/frm/core.rs b/crates/analytics/src/frm/core.rs index d2b79ad2773b..195266b4191a 100644 --- a/crates/analytics/src/frm/core.rs +++ b/crates/analytics/src/frm/core.rs @@ -9,7 +9,6 @@ use api_models::analytics::{ use error_stack::ResultExt; use router_env::{ logger, - metrics::add_attributes, tracing::{self, Instrument}, }; @@ -47,7 +46,7 @@ pub async fn get_metrics( &req.group_by_names.clone(), &merchant_id_scoped, &req.filters, - &req.time_series.map(|t| t.granularity), + req.time_series.map(|t| t.granularity), &req.time_range, ) .await @@ -66,13 +65,13 @@ pub async fn get_metrics( { let data = data?; - let attributes = &add_attributes([ + let attributes = router_env::metric_attributes!( ("metric_type", metric.to_string()), ("source", pool.to_string()), - ]); + ); let value = u64::try_from(data.len()); if let Ok(val) = value { - metrics::BUCKETS_FETCHED.record(&metrics::CONTEXT, val, attributes); + metrics::BUCKETS_FETCHED.record(val, attributes); logger::debug!("Attributes: {:?}, Buckets fetched: {}", attributes, val); } diff --git a/crates/analytics/src/frm/metrics.rs b/crates/analytics/src/frm/metrics.rs index 7f5b4ea32be3..b3780dfd3cbd 100644 --- a/crates/analytics/src/frm/metrics.rs +++ b/crates/analytics/src/frm/metrics.rs @@ -44,7 +44,7 @@ where dimensions: &[FrmDimensions], merchant_id: &common_utils::id_type::MerchantId, filters: &FrmFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult>; @@ -65,7 +65,7 @@ where dimensions: &[FrmDimensions], merchant_id: &common_utils::id_type::MerchantId, filters: &FrmFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { diff --git a/crates/analytics/src/frm/metrics/frm_blocked_rate.rs b/crates/analytics/src/frm/metrics/frm_blocked_rate.rs index 7154478c0267..00903cdcaef0 100644 --- a/crates/analytics/src/frm/metrics/frm_blocked_rate.rs +++ b/crates/analytics/src/frm/metrics/frm_blocked_rate.rs @@ -29,7 +29,7 @@ where dimensions: &[FrmDimensions], merchant_id: &common_utils::id_type::MerchantId, filters: &FrmFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> @@ -76,7 +76,7 @@ where query_builder.add_group_by_clause(dim).switch()?; } - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { granularity .set_group_by_clause(&mut query_builder) .switch()?; diff --git a/crates/analytics/src/frm/metrics/frm_triggered_attempts.rs b/crates/analytics/src/frm/metrics/frm_triggered_attempts.rs index 168a256ffa62..dd487cdc93f7 100644 --- a/crates/analytics/src/frm/metrics/frm_triggered_attempts.rs +++ b/crates/analytics/src/frm/metrics/frm_triggered_attempts.rs @@ -30,7 +30,7 @@ where dimensions: &[FrmDimensions], merchant_id: &common_utils::id_type::MerchantId, filters: &FrmFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -77,7 +77,7 @@ where .switch()?; } - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { granularity .set_group_by_clause(&mut query_builder) .attach_printable("Error adding granularity") diff --git a/crates/analytics/src/lib.rs b/crates/analytics/src/lib.rs index d5cb3c718dfb..0ad8886b8208 100644 --- a/crates/analytics/src/lib.rs +++ b/crates/analytics/src/lib.rs @@ -29,6 +29,7 @@ use hyperswitch_interfaces::secrets_interface::{ secret_state::{RawSecret, SecretStateContainer, SecuredSecret}, SecretManagementInterface, SecretsManagementError, }; +use refunds::distribution::{RefundDistribution, RefundDistributionRow}; pub use types::AnalyticsDomain; pub mod lambda_utils; pub mod utils; @@ -52,7 +53,7 @@ use api_models::analytics::{ sdk_events::{ SdkEventDimensions, SdkEventFilters, SdkEventMetrics, SdkEventMetricsBucketIdentifier, }, - Distribution, Granularity, TimeRange, + Granularity, PaymentDistributionBody, RefundDistributionBody, TimeRange, }; use clickhouse::ClickhouseClient; pub use clickhouse::ClickhouseConfig; @@ -115,7 +116,7 @@ impl AnalyticsProvider { dimensions: &[PaymentDimensions], auth: &AuthInfo, filters: &PaymentFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, ) -> types::MetricsResult> { // Metrics to get the fetch time for each payment metric @@ -215,11 +216,11 @@ impl AnalyticsProvider { pub async fn get_payment_distribution( &self, - distribution: &Distribution, + distribution: &PaymentDistributionBody, dimensions: &[PaymentDimensions], auth: &AuthInfo, filters: &PaymentFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, ) -> types::MetricsResult> { // Metrics to get the fetch time for each payment metric @@ -329,7 +330,7 @@ impl AnalyticsProvider { dimensions: &[PaymentIntentDimensions], auth: &AuthInfo, filters: &PaymentIntentFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, ) -> types::MetricsResult> { @@ -434,7 +435,7 @@ impl AnalyticsProvider { dimensions: &[RefundDimensions], auth: &AuthInfo, filters: &RefundFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, ) -> types::MetricsResult> { // Metrics to get the fetch time for each refund metric @@ -528,13 +529,123 @@ impl AnalyticsProvider { .await } + pub async fn get_refund_distribution( + &self, + distribution: &RefundDistributionBody, + dimensions: &[RefundDimensions], + auth: &AuthInfo, + filters: &RefundFilters, + granularity: &Option, + time_range: &TimeRange, + ) -> types::MetricsResult> { + // Metrics to get the fetch time for each payment metric + metrics::request::record_operation_time( + async { + match self { + Self::Sqlx(pool) => { + distribution.distribution_for + .load_distribution( + distribution, + dimensions, + auth, + filters, + granularity, + time_range, + pool, + ) + .await + } + Self::Clickhouse(pool) => { + distribution.distribution_for + .load_distribution( + distribution, + dimensions, + auth, + filters, + granularity, + time_range, + pool, + ) + .await + } + Self::CombinedCkh(sqlx_pool, ckh_pool) => { + let (ckh_result, sqlx_result) = tokio::join!(distribution.distribution_for + .load_distribution( + distribution, + dimensions, + auth, + filters, + granularity, + time_range, + ckh_pool, + ), + distribution.distribution_for + .load_distribution( + distribution, + dimensions, + auth, + filters, + granularity, + time_range, + sqlx_pool, + )); + match (&sqlx_result, &ckh_result) { + (Ok(ref sqlx_res), Ok(ref ckh_res)) if sqlx_res != ckh_res => { + router_env::logger::error!(clickhouse_result=?ckh_res, postgres_result=?sqlx_res, "Mismatch between clickhouse & postgres payments analytics distribution") + }, + _ => {} + + }; + + ckh_result + } + Self::CombinedSqlx(sqlx_pool, ckh_pool) => { + let (ckh_result, sqlx_result) = tokio::join!(distribution.distribution_for + .load_distribution( + distribution, + dimensions, + auth, + filters, + granularity, + time_range, + ckh_pool, + ), + distribution.distribution_for + .load_distribution( + distribution, + dimensions, + auth, + filters, + granularity, + time_range, + sqlx_pool, + )); + match (&sqlx_result, &ckh_result) { + (Ok(ref sqlx_res), Ok(ref ckh_res)) if sqlx_res != ckh_res => { + router_env::logger::error!(clickhouse_result=?ckh_res, postgres_result=?sqlx_res, "Mismatch between clickhouse & postgres payments analytics distribution") + }, + _ => {} + + }; + + sqlx_result + } + } + }, + &metrics::METRIC_FETCH_TIME, + &distribution.distribution_for, + self, + ) + .await + } + pub async fn get_frm_metrics( &self, metric: &FrmMetrics, dimensions: &[FrmDimensions], merchant_id: &common_utils::id_type::MerchantId, filters: &FrmFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, ) -> types::MetricsResult> { // Metrics to get the fetch time for each refund metric @@ -634,7 +745,7 @@ impl AnalyticsProvider { dimensions: &[DisputeDimensions], auth: &AuthInfo, filters: &DisputeFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, ) -> types::MetricsResult> { // Metrics to get the fetch time for each refund metric @@ -734,7 +845,7 @@ impl AnalyticsProvider { dimensions: &[SdkEventDimensions], publishable_key: &str, filters: &SdkEventFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, ) -> types::MetricsResult> { match self { @@ -799,7 +910,7 @@ impl AnalyticsProvider { metric: &AuthEventMetrics, merchant_id: &common_utils::id_type::MerchantId, publishable_key: &str, - granularity: &Option, + granularity: Option, time_range: &TimeRange, ) -> types::MetricsResult> { match self { @@ -830,7 +941,7 @@ impl AnalyticsProvider { dimensions: &[ApiEventDimensions], merchant_id: &common_utils::id_type::MerchantId, filters: &ApiEventFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, ) -> types::MetricsResult> { match self { @@ -858,21 +969,25 @@ impl AnalyticsProvider { tenant: &dyn storage_impl::config::TenantConfig, ) -> Self { match config { - AnalyticsConfig::Sqlx { sqlx } => { + AnalyticsConfig::Sqlx { sqlx, .. } => { Self::Sqlx(SqlxClient::from_conf(sqlx, tenant.get_schema()).await) } - AnalyticsConfig::Clickhouse { clickhouse } => Self::Clickhouse(ClickhouseClient { + AnalyticsConfig::Clickhouse { clickhouse, .. } => Self::Clickhouse(ClickhouseClient { config: Arc::new(clickhouse.clone()), database: tenant.get_clickhouse_database().to_string(), }), - AnalyticsConfig::CombinedCkh { sqlx, clickhouse } => Self::CombinedCkh( + AnalyticsConfig::CombinedCkh { + sqlx, clickhouse, .. + } => Self::CombinedCkh( SqlxClient::from_conf(sqlx, tenant.get_schema()).await, ClickhouseClient { config: Arc::new(clickhouse.clone()), database: tenant.get_clickhouse_database().to_string(), }, ), - AnalyticsConfig::CombinedSqlx { sqlx, clickhouse } => Self::CombinedSqlx( + AnalyticsConfig::CombinedSqlx { + sqlx, clickhouse, .. + } => Self::CombinedSqlx( SqlxClient::from_conf(sqlx, tenant.get_schema()).await, ClickhouseClient { config: Arc::new(clickhouse.clone()), @@ -889,20 +1004,35 @@ impl AnalyticsProvider { pub enum AnalyticsConfig { Sqlx { sqlx: Database, + forex_enabled: bool, }, Clickhouse { clickhouse: ClickhouseConfig, + forex_enabled: bool, }, CombinedCkh { sqlx: Database, clickhouse: ClickhouseConfig, + forex_enabled: bool, }, CombinedSqlx { sqlx: Database, clickhouse: ClickhouseConfig, + forex_enabled: bool, }, } +impl AnalyticsConfig { + pub fn get_forex_enabled(&self) -> bool { + match self { + Self::Sqlx { forex_enabled, .. } + | Self::Clickhouse { forex_enabled, .. } + | Self::CombinedCkh { forex_enabled, .. } + | Self::CombinedSqlx { forex_enabled, .. } => *forex_enabled, + } + } +} + #[async_trait::async_trait] impl SecretsHandler for AnalyticsConfig { async fn convert_to_raw_secret( @@ -913,7 +1043,7 @@ impl SecretsHandler for AnalyticsConfig { let decrypted_password = match analytics_config { // Todo: Perform kms decryption of clickhouse password Self::Clickhouse { .. } => masking::Secret::new(String::default()), - Self::Sqlx { sqlx } + Self::Sqlx { sqlx, .. } | Self::CombinedCkh { sqlx, .. } | Self::CombinedSqlx { sqlx, .. } => { secret_management_client @@ -923,26 +1053,46 @@ impl SecretsHandler for AnalyticsConfig { }; Ok(value.transition_state(|conf| match conf { - Self::Sqlx { sqlx } => Self::Sqlx { + Self::Sqlx { + sqlx, + forex_enabled, + } => Self::Sqlx { sqlx: Database { password: decrypted_password, ..sqlx }, + forex_enabled, }, - Self::Clickhouse { clickhouse } => Self::Clickhouse { clickhouse }, - Self::CombinedCkh { sqlx, clickhouse } => Self::CombinedCkh { + Self::Clickhouse { + clickhouse, + forex_enabled, + } => Self::Clickhouse { + clickhouse, + forex_enabled, + }, + Self::CombinedCkh { + sqlx, + clickhouse, + forex_enabled, + } => Self::CombinedCkh { sqlx: Database { password: decrypted_password, ..sqlx }, clickhouse, + forex_enabled, }, - Self::CombinedSqlx { sqlx, clickhouse } => Self::CombinedSqlx { + Self::CombinedSqlx { + sqlx, + clickhouse, + forex_enabled, + } => Self::CombinedSqlx { sqlx: Database { password: decrypted_password, ..sqlx }, clickhouse, + forex_enabled, }, })) } @@ -952,6 +1102,7 @@ impl Default for AnalyticsConfig { fn default() -> Self { Self::Sqlx { sqlx: Database::default(), + forex_enabled: false, } } } @@ -996,6 +1147,7 @@ pub enum AnalyticsFlow { GetSearchResults, GetDisputeFilters, GetDisputeMetrics, + GetSankey, } impl FlowMetric for AnalyticsFlow {} diff --git a/crates/analytics/src/metrics.rs b/crates/analytics/src/metrics.rs index 6222315a8c06..03eab289333d 100644 --- a/crates/analytics/src/metrics.rs +++ b/crates/analytics/src/metrics.rs @@ -1,9 +1,8 @@ -use router_env::{global_meter, histogram_metric, histogram_metric_u64, metrics_context}; +use router_env::{global_meter, histogram_metric_f64, histogram_metric_u64}; -metrics_context!(CONTEXT); global_meter!(GLOBAL_METER, "ROUTER_API"); -histogram_metric!(METRIC_FETCH_TIME, GLOBAL_METER); +histogram_metric_f64!(METRIC_FETCH_TIME, GLOBAL_METER); histogram_metric_u64!(BUCKETS_FETCHED, GLOBAL_METER); pub mod request; diff --git a/crates/analytics/src/metrics/request.rs b/crates/analytics/src/metrics/request.rs index 39375d391a3e..c30e34da8ee2 100644 --- a/crates/analytics/src/metrics/request.rs +++ b/crates/analytics/src/metrics/request.rs @@ -1,7 +1,5 @@ use std::time; -use router_env::metrics::add_attributes; - #[inline] pub async fn record_operation_time( future: F, @@ -14,12 +12,12 @@ where T: ToString, { let (result, time) = time_future(future).await; - let attributes = &add_attributes([ + let attributes = router_env::metric_attributes!( ("metric_name", metric_name.to_string()), ("source", source.to_string()), - ]); + ); let value = time.as_secs_f64(); - metric.record(&super::CONTEXT, value, attributes); + metric.record(value, attributes); router_env::logger::debug!("Attributes: {:?}, Time: {}", attributes, value); result diff --git a/crates/analytics/src/opensearch.rs b/crates/analytics/src/opensearch.rs index 149c77212a07..e85340e49d2d 100644 --- a/crates/analytics/src/opensearch.rs +++ b/crates/analytics/src/opensearch.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use api_models::{ analytics::search::SearchIndex, errors::types::{ApiError, ApiErrorResponse}, @@ -42,6 +44,10 @@ pub struct OpenSearchIndexes { pub payment_intents: String, pub refunds: String, pub disputes: String, + pub sessionizer_payment_attempts: String, + pub sessionizer_payment_intents: String, + pub sessionizer_refunds: String, + pub sessionizer_disputes: String, } #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash)] @@ -81,6 +87,10 @@ impl Default for OpenSearchConfig { payment_intents: "hyperswitch-payment-intent-events".to_string(), refunds: "hyperswitch-refund-events".to_string(), disputes: "hyperswitch-dispute-events".to_string(), + sessionizer_payment_attempts: "sessionizer-payment-attempt-events".to_string(), + sessionizer_payment_intents: "sessionizer-payment-intent-events".to_string(), + sessionizer_refunds: "sessionizer-refund-events".to_string(), + sessionizer_disputes: "sessionizer-dispute-events".to_string(), }, } } @@ -104,6 +114,8 @@ pub enum OpenSearchError { IndexAccessNotPermittedError(SearchIndex), #[error("Opensearch unknown error")] UnknownError, + #[error("Opensearch access forbidden error")] + AccessForbiddenError, } impl ErrorSwitch for QueryBuildingError { @@ -159,6 +171,12 @@ impl ErrorSwitch for OpenSearchError { Self::UnknownError => { ApiErrorResponse::InternalServerError(ApiError::new("IR", 6, "Unknown error", None)) } + Self::AccessForbiddenError => ApiErrorResponse::ForbiddenCommonResource(ApiError::new( + "IR", + 7, + "Access Forbidden error", + None, + )), } } } @@ -211,6 +229,14 @@ impl OpenSearchClient { SearchIndex::PaymentIntents => self.indexes.payment_intents.clone(), SearchIndex::Refunds => self.indexes.refunds.clone(), SearchIndex::Disputes => self.indexes.disputes.clone(), + SearchIndex::SessionizerPaymentAttempts => { + self.indexes.sessionizer_payment_attempts.clone() + } + SearchIndex::SessionizerPaymentIntents => { + self.indexes.sessionizer_payment_intents.clone() + } + SearchIndex::SessionizerRefunds => self.indexes.sessionizer_refunds.clone(), + SearchIndex::SessionizerDisputes => self.indexes.sessionizer_disputes.clone(), } } @@ -316,6 +342,36 @@ impl OpenSearchIndexes { )) })?; + when( + self.sessionizer_payment_attempts.is_default_or_empty(), + || { + Err(ApplicationError::InvalidConfigurationValueError( + "Opensearch Sessionizer Payment Attempts index must not be empty".into(), + )) + }, + )?; + + when( + self.sessionizer_payment_intents.is_default_or_empty(), + || { + Err(ApplicationError::InvalidConfigurationValueError( + "Opensearch Sessionizer Payment Intents index must not be empty".into(), + )) + }, + )?; + + when(self.sessionizer_refunds.is_default_or_empty(), || { + Err(ApplicationError::InvalidConfigurationValueError( + "Opensearch Sessionizer Refunds index must not be empty".into(), + )) + })?; + + when(self.sessionizer_disputes.is_default_or_empty(), || { + Err(ApplicationError::InvalidConfigurationValueError( + "Opensearch Sessionizer Disputes index must not be empty".into(), + )) + })?; + Ok(()) } } @@ -402,7 +458,8 @@ pub struct OpenSearchQueryBuilder { pub count: Option, pub filters: Vec<(String, Vec)>, pub time_range: Option, - pub search_params: Vec, + search_params: Vec, + case_sensitive_fields: HashSet<&'static str>, } impl OpenSearchQueryBuilder { @@ -415,6 +472,12 @@ impl OpenSearchQueryBuilder { count: Default::default(), filters: Default::default(), time_range: Default::default(), + case_sensitive_fields: HashSet::from([ + "customer_email.keyword", + "search_tags.keyword", + "card_last_4.keyword", + "payment_id.keyword", + ]), } } @@ -434,67 +497,35 @@ impl OpenSearchQueryBuilder { Ok(()) } - pub fn get_status_field(&self, index: &SearchIndex) -> &str { + pub fn get_status_field(&self, index: SearchIndex) -> &str { match index { - SearchIndex::Refunds => "refund_status.keyword", - SearchIndex::Disputes => "dispute_status.keyword", + SearchIndex::Refunds | SearchIndex::SessionizerRefunds => "refund_status.keyword", + SearchIndex::Disputes | SearchIndex::SessionizerDisputes => "dispute_status.keyword", _ => "status.keyword", } } - pub fn replace_status_field(&self, filters: &[Value], index: &SearchIndex) -> Vec { - filters - .iter() - .map(|filter| { - if let Some(terms) = filter.get("terms").and_then(|v| v.as_object()) { - let mut new_filter = filter.clone(); - if let Some(new_terms) = - new_filter.get_mut("terms").and_then(|v| v.as_object_mut()) - { - let key = "status.keyword"; - if let Some(status_terms) = terms.get(key) { - new_terms.remove(key); - new_terms.insert( - self.get_status_field(index).to_string(), - status_terms.clone(), - ); - } - } - new_filter - } else { - filter.clone() - } - }) - .collect() - } - - /// # Panics - /// - /// This function will panic if: - /// - /// * The structure of the JSON query is not as expected (e.g., missing keys or incorrect types). - /// - /// Ensure that the input data and the structure of the query are valid and correctly handled. - pub fn construct_payload(&self, indexes: &[SearchIndex]) -> QueryResult> { - let mut query_obj = Map::new(); - let mut bool_obj = Map::new(); + pub fn build_filter_array( + &self, + case_sensitive_filters: Vec<&(String, Vec)>, + ) -> Vec { let mut filter_array = Vec::new(); + if !self.query.is_empty() { + filter_array.push(json!({ + "multi_match": { + "type": "phrase", + "query": self.query, + "lenient": true + } + })); + } - filter_array.push(json!({ - "multi_match": { - "type": "phrase", - "query": self.query, - "lenient": true - } - })); - - let mut filters = self - .filters - .iter() + let case_sensitive_json_filters = case_sensitive_filters + .into_iter() .map(|(k, v)| json!({"terms": {k: v}})) .collect::>(); - filter_array.append(&mut filters); + filter_array.extend(case_sensitive_json_filters); if let Some(ref time_range) = self.time_range { let range = json!(time_range); @@ -505,8 +536,72 @@ impl OpenSearchQueryBuilder { })); } - let should_array = self - .search_params + filter_array + } + + pub fn build_case_insensitive_filters( + &self, + mut payload: Value, + case_insensitive_filters: &[&(String, Vec)], + auth_array: Vec, + index: SearchIndex, + ) -> Value { + let mut must_array = case_insensitive_filters + .iter() + .map(|(k, v)| { + let key = if *k == "status.keyword" { + self.get_status_field(index).to_string() + } else { + k.clone() + }; + json!({ + "bool": { + "must": [ + { + "bool": { + "should": v.iter().map(|value| { + json!({ + "term": { + format!("{}", key): { + "value": value, + "case_insensitive": true + } + } + }) + }).collect::>(), + "minimum_should_match": 1 + } + } + ] + } + }) + }) + .collect::>(); + + must_array.push(json!({ "bool": { + "must": [ + { + "bool": { + "should": auth_array, + "minimum_should_match": 1 + } + } + ] + }})); + + if let Some(query) = payload.get_mut("query") { + if let Some(bool_obj) = query.get_mut("bool") { + if let Some(bool_map) = bool_obj.as_object_mut() { + bool_map.insert("must".to_string(), Value::Array(must_array)); + } + } + } + + payload + } + + pub fn build_auth_array(&self) -> Vec { + self.search_params .iter() .map(|user_level| match user_level { AuthInfo::OrgLevel { org_id } => { @@ -525,11 +620,17 @@ impl OpenSearchQueryBuilder { }) } AuthInfo::MerchantLevel { - org_id: _, + org_id, merchant_ids, } => { let must_clauses = vec![ - // TODO: Add org_id field to the filters + json!({ + "term": { + "organization_id.keyword": { + "value": org_id + } + } + }), json!({ "terms": { "merchant_id.keyword": merchant_ids @@ -544,12 +645,18 @@ impl OpenSearchQueryBuilder { }) } AuthInfo::ProfileLevel { - org_id: _, + org_id, merchant_id, profile_ids, } => { let must_clauses = vec![ - // TODO: Add org_id field to the filters + json!({ + "term": { + "organization_id.keyword": { + "value": org_id + } + } + }), json!({ "term": { "merchant_id.keyword": { @@ -571,55 +678,58 @@ impl OpenSearchQueryBuilder { }) } }) - .collect::>(); + .collect::>() + } + + /// # Panics + /// + /// This function will panic if: + /// + /// * The structure of the JSON query is not as expected (e.g., missing keys or incorrect types). + /// + /// Ensure that the input data and the structure of the query are valid and correctly handled. + pub fn construct_payload(&self, indexes: &[SearchIndex]) -> QueryResult> { + let mut query_obj = Map::new(); + let mut bool_obj = Map::new(); + + let (case_sensitive_filters, case_insensitive_filters): (Vec<_>, Vec<_>) = self + .filters + .iter() + .partition(|(k, _)| self.case_sensitive_fields.contains(k.as_str())); + + let filter_array = self.build_filter_array(case_sensitive_filters); if !filter_array.is_empty() { bool_obj.insert("filter".to_string(), Value::Array(filter_array)); } - if !bool_obj.is_empty() { - query_obj.insert("bool".to_string(), Value::Object(bool_obj)); - } - let mut query = Map::new(); - query.insert("query".to_string(), Value::Object(query_obj)); + let should_array = self.build_auth_array(); + + query_obj.insert("bool".to_string(), Value::Object(bool_obj)); + + let mut sort_obj = Map::new(); + sort_obj.insert( + "@timestamp".to_string(), + json!({ + "order": "desc" + }), + ); Ok(indexes .iter() .map(|index| { - let updated_query = query - .get("query") - .and_then(|q| q.get("bool")) - .and_then(|b| b.get("filter")) - .and_then(|f| f.as_array()) - .map(|filters| self.replace_status_field(filters, index)) - .unwrap_or_default(); - let mut final_bool_obj = Map::new(); - if !updated_query.is_empty() { - final_bool_obj.insert("filter".to_string(), Value::Array(updated_query)); - } - if !should_array.is_empty() { - final_bool_obj.insert("should".to_string(), Value::Array(should_array.clone())); - final_bool_obj - .insert("minimum_should_match".to_string(), Value::Number(1.into())); - } - let mut final_query = Map::new(); - if !final_bool_obj.is_empty() { - final_query.insert("bool".to_string(), Value::Object(final_bool_obj)); - } - - let mut sort_obj = Map::new(); - sort_obj.insert( - "@timestamp".to_string(), - json!({ - "order": "desc" - }), - ); - let payload = json!({ - "query": Value::Object(final_query), + let mut payload = json!({ + "query": query_obj.clone(), "sort": [ - Value::Object(sort_obj) + Value::Object(sort_obj.clone()) ] }); + payload = self.build_case_insensitive_filters( + payload, + &case_insensitive_filters, + should_array.clone(), + *index, + ); payload }) .collect::>()) diff --git a/crates/analytics/src/payment_intents.rs b/crates/analytics/src/payment_intents.rs index 449dd94788c3..809be1b20a1f 100644 --- a/crates/analytics/src/payment_intents.rs +++ b/crates/analytics/src/payment_intents.rs @@ -2,6 +2,7 @@ pub mod accumulator; mod core; pub mod filters; pub mod metrics; +pub mod sankey; pub mod types; pub use accumulator::{PaymentIntentMetricAccumulator, PaymentIntentMetricsAccumulator}; @@ -10,4 +11,4 @@ pub trait PaymentIntentAnalytics: { } -pub use self::core::{get_filters, get_metrics}; +pub use self::core::{get_filters, get_metrics, get_sankey}; diff --git a/crates/analytics/src/payment_intents/accumulator.rs b/crates/analytics/src/payment_intents/accumulator.rs index 8fd98a1e73cc..d8f27501b567 100644 --- a/crates/analytics/src/payment_intents/accumulator.rs +++ b/crates/analytics/src/payment_intents/accumulator.rs @@ -1,5 +1,6 @@ use api_models::analytics::payment_intents::PaymentIntentMetricsBucketValue; use bigdecimal::ToPrimitive; +use diesel_models::enums as storage_enums; use super::metrics::PaymentIntentMetricRow; @@ -7,8 +8,11 @@ use super::metrics::PaymentIntentMetricRow; pub struct PaymentIntentMetricsAccumulator { pub successful_smart_retries: CountAccumulator, pub total_smart_retries: CountAccumulator, - pub smart_retried_amount: SumAccumulator, + pub smart_retried_amount: SmartRetriedAmountAccumulator, pub payment_intent_count: CountAccumulator, + pub payments_success_rate: PaymentsSuccessRateAccumulator, + pub payment_processed_amount: ProcessedAmountAccumulator, + pub payments_distribution: PaymentsDistributionAccumulator, } #[derive(Debug, Default)] @@ -38,9 +42,31 @@ pub trait PaymentIntentMetricAccumulator { } #[derive(Debug, Default)] -#[repr(transparent)] -pub struct SumAccumulator { - pub total: Option, +pub struct SmartRetriedAmountAccumulator { + pub amount: Option, + pub amount_without_retries: Option, +} + +#[derive(Debug, Default)] +pub struct PaymentsSuccessRateAccumulator { + pub success: u32, + pub success_without_retries: u32, + pub total: u32, +} + +#[derive(Debug, Default)] +pub struct ProcessedAmountAccumulator { + pub count_with_retries: Option, + pub total_with_retries: Option, + pub count_without_retries: Option, + pub total_without_retries: Option, +} + +#[derive(Debug, Default)] +pub struct PaymentsDistributionAccumulator { + pub success_without_retries: u32, + pub failed_without_retries: u32, + pub total: u32, } impl PaymentIntentMetricAccumulator for CountAccumulator { @@ -59,32 +85,276 @@ impl PaymentIntentMetricAccumulator for CountAccumulator { } } -impl PaymentIntentMetricAccumulator for SumAccumulator { - type MetricOutput = Option; +impl PaymentIntentMetricAccumulator for SmartRetriedAmountAccumulator { + type MetricOutput = (Option, Option, Option, Option); #[inline] fn add_metrics_bucket(&mut self, metrics: &PaymentIntentMetricRow) { - self.total = match ( - self.total, + self.amount = match ( + self.amount, metrics.total.as_ref().and_then(ToPrimitive::to_i64), ) { (None, None) => None, (None, i @ Some(_)) | (i @ Some(_), None) => i, (Some(a), Some(b)) => Some(a + b), + }; + if metrics.first_attempt.unwrap_or(0) == 1 { + self.amount_without_retries = match ( + self.amount_without_retries, + metrics.total.as_ref().and_then(ToPrimitive::to_i64), + ) { + (None, None) => None, + (None, i @ Some(_)) | (i @ Some(_), None) => i, + (Some(a), Some(b)) => Some(a + b), + } + } else { + self.amount_without_retries = Some(0); } } #[inline] fn collect(self) -> Self::MetricOutput { - self.total.and_then(|i| u64::try_from(i).ok()) + let with_retries = self.amount.and_then(|i| u64::try_from(i).ok()).or(Some(0)); + let without_retries = self + .amount_without_retries + .and_then(|i| u64::try_from(i).ok()) + .or(Some(0)); + (with_retries, without_retries, Some(0), Some(0)) + } +} + +impl PaymentIntentMetricAccumulator for PaymentsSuccessRateAccumulator { + type MetricOutput = ( + Option, + Option, + Option, + Option, + Option, + ); + + fn add_metrics_bucket(&mut self, metrics: &PaymentIntentMetricRow) { + if let Some(ref status) = metrics.status { + if status.as_ref() == &storage_enums::IntentStatus::Succeeded { + if let Some(success) = metrics + .count + .and_then(|success| u32::try_from(success).ok()) + { + self.success += success; + if metrics.first_attempt.unwrap_or(0) == 1 { + self.success_without_retries += success; + } + } + } + if status.as_ref() != &storage_enums::IntentStatus::RequiresCustomerAction + && status.as_ref() != &storage_enums::IntentStatus::RequiresPaymentMethod + && status.as_ref() != &storage_enums::IntentStatus::RequiresMerchantAction + && status.as_ref() != &storage_enums::IntentStatus::RequiresConfirmation + { + if let Some(total) = metrics.count.and_then(|total| u32::try_from(total).ok()) { + self.total += total; + } + } + } + } + + fn collect(self) -> Self::MetricOutput { + if self.total == 0 { + (None, None, None, None, None) + } else { + let success = Some(self.success); + let success_without_retries = Some(self.success_without_retries); + let total = Some(self.total); + + let success_rate = match (success, total) { + (Some(s), Some(t)) if t > 0 => Some(f64::from(s) * 100.0 / f64::from(t)), + _ => None, + }; + + let success_without_retries_rate = match (success_without_retries, total) { + (Some(s), Some(t)) if t > 0 => Some(f64::from(s) * 100.0 / f64::from(t)), + _ => None, + }; + + ( + success, + success_without_retries, + total, + success_rate, + success_without_retries_rate, + ) + } + } +} + +impl PaymentIntentMetricAccumulator for ProcessedAmountAccumulator { + type MetricOutput = ( + Option, + Option, + Option, + Option, + Option, + Option, + ); + #[inline] + fn add_metrics_bucket(&mut self, metrics: &PaymentIntentMetricRow) { + self.total_with_retries = match ( + self.total_with_retries, + metrics.total.as_ref().and_then(ToPrimitive::to_i64), + ) { + (None, None) => None, + (None, i @ Some(_)) | (i @ Some(_), None) => i, + (Some(a), Some(b)) => Some(a + b), + }; + + self.count_with_retries = match (self.count_with_retries, metrics.count) { + (None, None) => None, + (None, i @ Some(_)) | (i @ Some(_), None) => i, + (Some(a), Some(b)) => Some(a + b), + }; + + if metrics.first_attempt.unwrap_or(0) == 1 { + self.total_without_retries = match ( + self.total_without_retries, + metrics.total.as_ref().and_then(ToPrimitive::to_i64), + ) { + (None, None) => None, + (None, i @ Some(_)) | (i @ Some(_), None) => i, + (Some(a), Some(b)) => Some(a + b), + }; + + self.count_without_retries = match (self.count_without_retries, metrics.count) { + (None, None) => None, + (None, i @ Some(_)) | (i @ Some(_), None) => i, + (Some(a), Some(b)) => Some(a + b), + }; + } + } + #[inline] + fn collect(self) -> Self::MetricOutput { + let total_with_retries = u64::try_from(self.total_with_retries.unwrap_or(0)).ok(); + let count_with_retries = self.count_with_retries.and_then(|i| u64::try_from(i).ok()); + + let total_without_retries = u64::try_from(self.total_without_retries.unwrap_or(0)).ok(); + let count_without_retries = self + .count_without_retries + .and_then(|i| u64::try_from(i).ok()); + + ( + total_with_retries, + count_with_retries, + total_without_retries, + count_without_retries, + Some(0), + Some(0), + ) + } +} + +impl PaymentIntentMetricAccumulator for PaymentsDistributionAccumulator { + type MetricOutput = (Option, Option); + + fn add_metrics_bucket(&mut self, metrics: &PaymentIntentMetricRow) { + let first_attempt = metrics.first_attempt.unwrap_or(0); + if let Some(ref status) = metrics.status { + if status.as_ref() == &storage_enums::IntentStatus::Succeeded { + if let Some(success) = metrics + .count + .and_then(|success| u32::try_from(success).ok()) + { + if first_attempt == 1 { + self.success_without_retries += success; + } + } + } + if let Some(failed) = metrics.count.and_then(|failed| u32::try_from(failed).ok()) { + if first_attempt == 0 + || (first_attempt == 1 + && status.as_ref() == &storage_enums::IntentStatus::Failed) + { + self.failed_without_retries += failed; + } + } + + if status.as_ref() != &storage_enums::IntentStatus::RequiresCustomerAction + && status.as_ref() != &storage_enums::IntentStatus::RequiresPaymentMethod + && status.as_ref() != &storage_enums::IntentStatus::RequiresMerchantAction + && status.as_ref() != &storage_enums::IntentStatus::RequiresConfirmation + { + if let Some(total) = metrics.count.and_then(|total| u32::try_from(total).ok()) { + self.total += total; + } + } + } + } + + fn collect(self) -> Self::MetricOutput { + if self.total == 0 { + (None, None) + } else { + let success_without_retries = Some(self.success_without_retries); + let failed_without_retries = Some(self.failed_without_retries); + let total = Some(self.total); + + let success_rate_without_retries = match (success_without_retries, total) { + (Some(s), Some(t)) if t > 0 => Some(f64::from(s) * 100.0 / f64::from(t)), + _ => None, + }; + + let failed_rate_without_retries = match (failed_without_retries, total) { + (Some(s), Some(t)) if t > 0 => Some(f64::from(s) * 100.0 / f64::from(t)), + _ => None, + }; + (success_rate_without_retries, failed_rate_without_retries) + } } } impl PaymentIntentMetricsAccumulator { pub fn collect(self) -> PaymentIntentMetricsBucketValue { + let ( + successful_payments, + successful_payments_without_smart_retries, + total_payments, + payments_success_rate, + payments_success_rate_without_smart_retries, + ) = self.payments_success_rate.collect(); + let ( + smart_retried_amount, + smart_retried_amount_without_smart_retries, + smart_retried_amount_in_usd, + smart_retried_amount_without_smart_retries_in_usd, + ) = self.smart_retried_amount.collect(); + let ( + payment_processed_amount, + payment_processed_count, + payment_processed_amount_without_smart_retries, + payment_processed_count_without_smart_retries, + payment_processed_amount_in_usd, + payment_processed_amount_without_smart_retries_in_usd, + ) = self.payment_processed_amount.collect(); + let ( + payments_success_rate_distribution_without_smart_retries, + payments_failure_rate_distribution_without_smart_retries, + ) = self.payments_distribution.collect(); PaymentIntentMetricsBucketValue { successful_smart_retries: self.successful_smart_retries.collect(), total_smart_retries: self.total_smart_retries.collect(), - smart_retried_amount: self.smart_retried_amount.collect(), + smart_retried_amount, + smart_retried_amount_in_usd, + smart_retried_amount_without_smart_retries, + smart_retried_amount_without_smart_retries_in_usd, payment_intent_count: self.payment_intent_count.collect(), + successful_payments, + successful_payments_without_smart_retries, + total_payments, + payments_success_rate, + payments_success_rate_without_smart_retries, + payment_processed_amount, + payment_processed_count, + payment_processed_amount_without_smart_retries, + payment_processed_count_without_smart_retries, + payments_success_rate_distribution_without_smart_retries, + payments_failure_rate_distribution_without_smart_retries, + payment_processed_amount_in_usd, + payment_processed_amount_without_smart_retries_in_usd, } } } diff --git a/crates/analytics/src/payment_intents/core.rs b/crates/analytics/src/payment_intents/core.rs index f8a5c48986ab..0a512c419ec0 100644 --- a/crates/analytics/src/payment_intents/core.rs +++ b/crates/analytics/src/payment_intents/core.rs @@ -6,20 +6,23 @@ use api_models::analytics::{ MetricsBucketResponse, PaymentIntentDimensions, PaymentIntentMetrics, PaymentIntentMetricsBucketIdentifier, }, - AnalyticsMetadata, GetPaymentIntentFiltersRequest, GetPaymentIntentMetricRequest, - MetricsResponse, PaymentIntentFilterValue, PaymentIntentFiltersResponse, + GetPaymentIntentFiltersRequest, GetPaymentIntentMetricRequest, PaymentIntentFilterValue, + PaymentIntentFiltersResponse, PaymentIntentsAnalyticsMetadata, PaymentIntentsMetricsResponse, }; -use common_utils::errors::CustomResult; +use bigdecimal::ToPrimitive; +use common_enums::Currency; +use common_utils::{errors::CustomResult, types::TimeRange}; +use currency_conversion::{conversion::convert, types::ExchangeRates}; use error_stack::ResultExt; use router_env::{ instrument, logger, - metrics::add_attributes, tracing::{self, Instrument}, }; use super::{ filters::{get_payment_intent_filter_for_dimension, PaymentIntentFilterRow}, metrics::PaymentIntentMetricRow, + sankey::{get_sankey_data, SankeyRow}, PaymentIntentMetricsAccumulator, }; use crate::{ @@ -41,12 +44,34 @@ pub enum TaskType { ), } +#[instrument(skip_all)] +pub async fn get_sankey( + pool: &AnalyticsProvider, + auth: &AuthInfo, + req: TimeRange, +) -> AnalyticsResult> { + match pool { + AnalyticsProvider::Sqlx(_) => Err(AnalyticsError::NotImplemented( + "Sankey not implemented for sqlx", + ))?, + AnalyticsProvider::Clickhouse(ckh_pool) + | AnalyticsProvider::CombinedCkh(_, ckh_pool) + | AnalyticsProvider::CombinedSqlx(_, ckh_pool) => { + let sankey_rows = get_sankey_data(ckh_pool, auth, &req) + .await + .change_context(AnalyticsError::UnknownError)?; + Ok(sankey_rows) + } + } +} + #[instrument(skip_all)] pub async fn get_metrics( pool: &AnalyticsProvider, + ex_rates: &Option, auth: &AuthInfo, req: GetPaymentIntentMetricRequest, -) -> AnalyticsResult> { +) -> AnalyticsResult> { let mut metrics_accumulator: HashMap< PaymentIntentMetricsBucketIdentifier, PaymentIntentMetricsAccumulator, @@ -72,7 +97,7 @@ pub async fn get_metrics( &req.group_by_names.clone(), &auth_scoped, &req.filters, - &req.time_series.map(|t| t.granularity), + req.time_series.map(|t| t.granularity), &req.time_range, ) .await @@ -92,14 +117,14 @@ pub async fn get_metrics( match task_type { TaskType::MetricTask(metric, data) => { let data = data?; - let attributes = &add_attributes([ + let attributes = router_env::metric_attributes!( ("metric_type", metric.to_string()), ("source", pool.to_string()), - ]); + ); let value = u64::try_from(data.len()); if let Ok(val) = value { - metrics::BUCKETS_FETCHED.record(&metrics::CONTEXT, val, attributes); + metrics::BUCKETS_FETCHED.record(val, attributes); logger::debug!("Attributes: {:?}, Buckets fetched: {}", attributes, val); } @@ -107,18 +132,35 @@ pub async fn get_metrics( logger::debug!(bucket_id=?id, bucket_value=?value, "Bucket row for metric {metric}"); let metrics_builder = metrics_accumulator.entry(id).or_default(); match metric { - PaymentIntentMetrics::SuccessfulSmartRetries => metrics_builder - .successful_smart_retries - .add_metrics_bucket(&value), - PaymentIntentMetrics::TotalSmartRetries => metrics_builder + PaymentIntentMetrics::SuccessfulSmartRetries + | PaymentIntentMetrics::SessionizedSuccessfulSmartRetries => { + metrics_builder + .successful_smart_retries + .add_metrics_bucket(&value) + } + PaymentIntentMetrics::TotalSmartRetries + | PaymentIntentMetrics::SessionizedTotalSmartRetries => metrics_builder .total_smart_retries .add_metrics_bucket(&value), - PaymentIntentMetrics::SmartRetriedAmount => metrics_builder + PaymentIntentMetrics::SmartRetriedAmount + | PaymentIntentMetrics::SessionizedSmartRetriedAmount => metrics_builder .smart_retried_amount .add_metrics_bucket(&value), - PaymentIntentMetrics::PaymentIntentCount => metrics_builder + PaymentIntentMetrics::PaymentIntentCount + | PaymentIntentMetrics::SessionizedPaymentIntentCount => metrics_builder .payment_intent_count .add_metrics_bucket(&value), + PaymentIntentMetrics::PaymentsSuccessRate + | PaymentIntentMetrics::SessionizedPaymentsSuccessRate => metrics_builder + .payments_success_rate + .add_metrics_bucket(&value), + PaymentIntentMetrics::SessionizedPaymentProcessedAmount + | PaymentIntentMetrics::PaymentProcessedAmount => metrics_builder + .payment_processed_amount + .add_metrics_bucket(&value), + PaymentIntentMetrics::SessionizedPaymentsDistribution => metrics_builder + .payments_distribution + .add_metrics_bucket(&value), } } @@ -131,18 +173,191 @@ pub async fn get_metrics( } } + let mut success = 0; + let mut success_without_smart_retries = 0; + let mut total_smart_retried_amount = 0; + let mut total_smart_retried_amount_in_usd = 0; + let mut total_smart_retried_amount_without_smart_retries = 0; + let mut total_smart_retried_amount_without_smart_retries_in_usd = 0; + let mut total = 0; + let mut total_payment_processed_amount = 0; + let mut total_payment_processed_amount_in_usd = 0; + let mut total_payment_processed_count = 0; + let mut total_payment_processed_amount_without_smart_retries = 0; + let mut total_payment_processed_amount_without_smart_retries_in_usd = 0; + let mut total_payment_processed_count_without_smart_retries = 0; let query_data: Vec = metrics_accumulator .into_iter() - .map(|(id, val)| MetricsBucketResponse { - values: val.collect(), - dimensions: id, + .map(|(id, val)| { + let mut collected_values = val.collect(); + if let Some(success_count) = collected_values.successful_payments { + success += success_count; + } + if let Some(success_count) = collected_values.successful_payments_without_smart_retries + { + success_without_smart_retries += success_count; + } + if let Some(total_count) = collected_values.total_payments { + total += total_count; + } + if let Some(retried_amount) = collected_values.smart_retried_amount { + let amount_in_usd = if let Some(ex_rates) = ex_rates { + id.currency + .and_then(|currency| { + i64::try_from(retried_amount) + .inspect_err(|e| logger::error!("Amount conversion error: {:?}", e)) + .ok() + .and_then(|amount_i64| { + convert(ex_rates, currency, Currency::USD, amount_i64) + .inspect_err(|e| { + logger::error!("Currency conversion error: {:?}", e) + }) + .ok() + }) + }) + .map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64()) + .unwrap_or_default() + } else { + None + }; + collected_values.smart_retried_amount_in_usd = amount_in_usd; + total_smart_retried_amount += retried_amount; + total_smart_retried_amount_in_usd += amount_in_usd.unwrap_or(0); + } + if let Some(retried_amount) = + collected_values.smart_retried_amount_without_smart_retries + { + let amount_in_usd = if let Some(ex_rates) = ex_rates { + id.currency + .and_then(|currency| { + i64::try_from(retried_amount) + .inspect_err(|e| logger::error!("Amount conversion error: {:?}", e)) + .ok() + .and_then(|amount_i64| { + convert(ex_rates, currency, Currency::USD, amount_i64) + .inspect_err(|e| { + logger::error!("Currency conversion error: {:?}", e) + }) + .ok() + }) + }) + .map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64()) + .unwrap_or_default() + } else { + None + }; + collected_values.smart_retried_amount_without_smart_retries_in_usd = amount_in_usd; + total_smart_retried_amount_without_smart_retries += retried_amount; + total_smart_retried_amount_without_smart_retries_in_usd += + amount_in_usd.unwrap_or(0); + } + if let Some(amount) = collected_values.payment_processed_amount { + let amount_in_usd = if let Some(ex_rates) = ex_rates { + id.currency + .and_then(|currency| { + i64::try_from(amount) + .inspect_err(|e| logger::error!("Amount conversion error: {:?}", e)) + .ok() + .and_then(|amount_i64| { + convert(ex_rates, currency, Currency::USD, amount_i64) + .inspect_err(|e| { + logger::error!("Currency conversion error: {:?}", e) + }) + .ok() + }) + }) + .map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64()) + .unwrap_or_default() + } else { + None + }; + collected_values.payment_processed_amount_in_usd = amount_in_usd; + total_payment_processed_amount_in_usd += amount_in_usd.unwrap_or(0); + total_payment_processed_amount += amount; + } + if let Some(count) = collected_values.payment_processed_count { + total_payment_processed_count += count; + } + if let Some(amount) = collected_values.payment_processed_amount_without_smart_retries { + let amount_in_usd = if let Some(ex_rates) = ex_rates { + id.currency + .and_then(|currency| { + i64::try_from(amount) + .inspect_err(|e| logger::error!("Amount conversion error: {:?}", e)) + .ok() + .and_then(|amount_i64| { + convert(ex_rates, currency, Currency::USD, amount_i64) + .inspect_err(|e| { + logger::error!("Currency conversion error: {:?}", e) + }) + .ok() + }) + }) + .map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64()) + .unwrap_or_default() + } else { + None + }; + collected_values.payment_processed_amount_without_smart_retries_in_usd = + amount_in_usd; + total_payment_processed_amount_without_smart_retries_in_usd += + amount_in_usd.unwrap_or(0); + total_payment_processed_amount_without_smart_retries += amount; + } + if let Some(count) = collected_values.payment_processed_count_without_smart_retries { + total_payment_processed_count_without_smart_retries += count; + } + MetricsBucketResponse { + values: collected_values, + dimensions: id, + } }) .collect(); - - Ok(MetricsResponse { + let total_success_rate = match (success, total) { + (s, t) if t > 0 => Some(f64::from(s) * 100.0 / f64::from(t)), + _ => None, + }; + let total_success_rate_without_smart_retries = match (success_without_smart_retries, total) { + (s, t) if t > 0 => Some(f64::from(s) * 100.0 / f64::from(t)), + _ => None, + }; + Ok(PaymentIntentsMetricsResponse { query_data, - meta_data: [AnalyticsMetadata { - current_time_range: req.time_range, + meta_data: [PaymentIntentsAnalyticsMetadata { + total_success_rate, + total_success_rate_without_smart_retries, + total_smart_retried_amount: Some(total_smart_retried_amount), + total_smart_retried_amount_without_smart_retries: Some( + total_smart_retried_amount_without_smart_retries, + ), + total_payment_processed_amount: Some(total_payment_processed_amount), + total_payment_processed_amount_without_smart_retries: Some( + total_payment_processed_amount_without_smart_retries, + ), + total_smart_retried_amount_in_usd: if ex_rates.is_some() { + Some(total_smart_retried_amount_in_usd) + } else { + None + }, + total_smart_retried_amount_without_smart_retries_in_usd: if ex_rates.is_some() { + Some(total_smart_retried_amount_without_smart_retries_in_usd) + } else { + None + }, + total_payment_processed_amount_in_usd: if ex_rates.is_some() { + Some(total_payment_processed_amount_in_usd) + } else { + None + }, + total_payment_processed_amount_without_smart_retries_in_usd: if ex_rates.is_some() { + Some(total_payment_processed_amount_without_smart_retries_in_usd) + } else { + None + }, + total_payment_processed_count: Some(total_payment_processed_count), + total_payment_processed_count_without_smart_retries: Some( + total_payment_processed_count_without_smart_retries, + ), }], }) } @@ -217,6 +432,15 @@ pub async fn get_filters( PaymentIntentDimensions::PaymentIntentStatus => fil.status.map(|i| i.as_ref().to_string()), PaymentIntentDimensions::Currency => fil.currency.map(|i| i.as_ref().to_string()), PaymentIntentDimensions::ProfileId => fil.profile_id, + PaymentIntentDimensions::Connector => fil.connector, + PaymentIntentDimensions::AuthType => fil.authentication_type.map(|i| i.as_ref().to_string()), + PaymentIntentDimensions::PaymentMethod => fil.payment_method, + PaymentIntentDimensions::PaymentMethodType => fil.payment_method_type, + PaymentIntentDimensions::CardNetwork => fil.card_network, + PaymentIntentDimensions::MerchantId => fil.merchant_id, + PaymentIntentDimensions::CardLast4 => fil.card_last_4, + PaymentIntentDimensions::CardIssuer => fil.card_issuer, + PaymentIntentDimensions::ErrorReason => fil.error_reason, }) .collect::>(); res.query_data.push(PaymentIntentFilterValue { diff --git a/crates/analytics/src/payment_intents/filters.rs b/crates/analytics/src/payment_intents/filters.rs index e81b050214c5..1468a67570aa 100644 --- a/crates/analytics/src/payment_intents/filters.rs +++ b/crates/analytics/src/payment_intents/filters.rs @@ -1,6 +1,6 @@ use api_models::analytics::{payment_intents::PaymentIntentDimensions, Granularity, TimeRange}; use common_utils::errors::ReportSwitchExt; -use diesel_models::enums::{Currency, IntentStatus}; +use diesel_models::enums::{AuthenticationType, Currency, IntentStatus}; use error_stack::ResultExt; use time::PrimitiveDateTime; @@ -54,4 +54,14 @@ pub struct PaymentIntentFilterRow { pub status: Option>, pub currency: Option>, pub profile_id: Option, + pub connector: Option, + pub authentication_type: Option>, + pub payment_method: Option, + pub payment_method_type: Option, + pub card_network: Option, + pub merchant_id: Option, + pub card_last_4: Option, + pub card_issuer: Option, + pub error_reason: Option, + pub customer_id: Option, } diff --git a/crates/analytics/src/payment_intents/metrics.rs b/crates/analytics/src/payment_intents/metrics.rs index f27a02368aec..c063b3a7c044 100644 --- a/crates/analytics/src/payment_intents/metrics.rs +++ b/crates/analytics/src/payment_intents/metrics.rs @@ -17,11 +17,16 @@ use crate::{ }; mod payment_intent_count; +mod payment_processed_amount; +mod payments_success_rate; +mod sessionized_metrics; mod smart_retried_amount; mod successful_smart_retries; mod total_smart_retries; use payment_intent_count::PaymentIntentCount; +use payment_processed_amount::PaymentProcessedAmount; +use payments_success_rate::PaymentsSuccessRate; use smart_retried_amount::SmartRetriedAmount; use successful_smart_retries::SuccessfulSmartRetries; use total_smart_retries::TotalSmartRetries; @@ -31,6 +36,16 @@ pub struct PaymentIntentMetricRow { pub status: Option>, pub currency: Option>, pub profile_id: Option, + pub connector: Option, + pub authentication_type: Option>, + pub payment_method: Option, + pub payment_method_type: Option, + pub card_network: Option, + pub merchant_id: Option, + pub card_last_4: Option, + pub card_issuer: Option, + pub error_reason: Option, + pub first_attempt: Option, pub total: Option, pub count: Option, #[serde(with = "common_utils::custom_serde::iso8601::option")] @@ -51,7 +66,7 @@ where dimensions: &[PaymentIntentDimensions], auth: &AuthInfo, filters: &PaymentIntentFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult>; @@ -72,7 +87,7 @@ where dimensions: &[PaymentIntentDimensions], auth: &AuthInfo, filters: &PaymentIntentFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> @@ -98,6 +113,51 @@ where .load_metrics(dimensions, auth, filters, granularity, time_range, pool) .await } + Self::PaymentsSuccessRate => { + PaymentsSuccessRate + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } + Self::PaymentProcessedAmount => { + PaymentProcessedAmount + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } + Self::SessionizedSuccessfulSmartRetries => { + sessionized_metrics::SuccessfulSmartRetries + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } + Self::SessionizedTotalSmartRetries => { + sessionized_metrics::TotalSmartRetries + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } + Self::SessionizedSmartRetriedAmount => { + sessionized_metrics::SmartRetriedAmount + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } + Self::SessionizedPaymentIntentCount => { + sessionized_metrics::PaymentIntentCount + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } + Self::SessionizedPaymentsSuccessRate => { + sessionized_metrics::PaymentsSuccessRate + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } + Self::SessionizedPaymentProcessedAmount => { + sessionized_metrics::PaymentProcessedAmount + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } + Self::SessionizedPaymentsDistribution => { + sessionized_metrics::PaymentsDistribution + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } } } } diff --git a/crates/analytics/src/payment_intents/metrics/payment_intent_count.rs b/crates/analytics/src/payment_intents/metrics/payment_intent_count.rs index 4632cbe9f370..424901ca7e41 100644 --- a/crates/analytics/src/payment_intents/metrics/payment_intent_count.rs +++ b/crates/analytics/src/payment_intents/metrics/payment_intent_count.rs @@ -35,7 +35,7 @@ where dimensions: &[PaymentIntentDimensions], auth: &AuthInfo, filters: &PaymentIntentFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> @@ -82,7 +82,7 @@ where .switch()?; } - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { granularity .set_group_by_clause(&mut query_builder) .attach_printable("Error adding granularity") @@ -101,6 +101,15 @@ where i.status.as_ref().map(|i| i.0), i.currency.as_ref().map(|i| i.0), i.profile_id.clone(), + i.connector.clone(), + i.authentication_type.as_ref().map(|i| i.0), + i.payment_method.clone(), + i.payment_method_type.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.clone(), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/payment_intents/metrics/payment_processed_amount.rs b/crates/analytics/src/payment_intents/metrics/payment_processed_amount.rs new file mode 100644 index 000000000000..d913bfe9a344 --- /dev/null +++ b/crates/analytics/src/payment_intents/metrics/payment_processed_amount.rs @@ -0,0 +1,161 @@ +use std::collections::HashSet; + +use api_models::analytics::{ + payment_intents::{ + PaymentIntentDimensions, PaymentIntentFilters, PaymentIntentMetricsBucketIdentifier, + }, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use diesel_models::enums as storage_enums; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::PaymentIntentMetricRow; +use crate::{ + enums::AuthInfo, + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(super) struct PaymentProcessedAmount; + +#[async_trait::async_trait] +impl super::PaymentIntentMetric for PaymentProcessedAmount +where + T: AnalyticsDataSource + super::PaymentIntentMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[PaymentIntentDimensions], + auth: &AuthInfo, + filters: &PaymentIntentFilters, + granularity: Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> + { + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::PaymentIntent); + + let mut dimensions = dimensions.to_vec(); + + dimensions.push(PaymentIntentDimensions::PaymentIntentStatus); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + + query_builder.add_select_column("currency").switch()?; + + query_builder + .add_select_column(Aggregate::Sum { + field: "amount", + alias: Some("total"), + }) + .switch()?; + + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + + query_builder + .add_group_by_clause("currency") + .attach_printable("Error grouping by currency") + .switch()?; + + if let Some(granularity) = granularity { + granularity + .set_group_by_clause(&mut query_builder) + .attach_printable("Error adding granularity") + .switch()?; + } + + query_builder + .add_filter_clause( + PaymentIntentDimensions::PaymentIntentStatus, + storage_enums::IntentStatus::Succeeded, + ) + .switch()?; + + query_builder + .execute_query::(pool) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(|i| { + Ok(( + PaymentIntentMetricsBucketIdentifier::new( + None, + i.currency.as_ref().map(|i| i.0), + i.profile_id.clone(), + i.connector.clone(), + i.authentication_type.as_ref().map(|i| i.0), + i.payment_method.clone(), + i.payment_method_type.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/payment_intents/metrics/payments_success_rate.rs b/crates/analytics/src/payment_intents/metrics/payments_success_rate.rs new file mode 100644 index 000000000000..dda3e37d39a0 --- /dev/null +++ b/crates/analytics/src/payment_intents/metrics/payments_success_rate.rs @@ -0,0 +1,137 @@ +use std::collections::HashSet; + +use api_models::analytics::{ + payment_intents::{ + PaymentIntentDimensions, PaymentIntentFilters, PaymentIntentMetricsBucketIdentifier, + }, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::PaymentIntentMetricRow; +use crate::{ + enums::AuthInfo, + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(super) struct PaymentsSuccessRate; + +#[async_trait::async_trait] +impl super::PaymentIntentMetric for PaymentsSuccessRate +where + T: AnalyticsDataSource + super::PaymentIntentMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[PaymentIntentDimensions], + auth: &AuthInfo, + filters: &PaymentIntentFilters, + granularity: Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> + { + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::PaymentIntent); + + let mut dimensions = dimensions.to_vec(); + + dimensions.push(PaymentIntentDimensions::PaymentIntentStatus); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + + if let Some(granularity) = granularity { + granularity + .set_group_by_clause(&mut query_builder) + .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(( + PaymentIntentMetricsBucketIdentifier::new( + None, + i.currency.as_ref().map(|i| i.0), + i.profile_id.clone(), + i.connector.clone(), + i.authentication_type.as_ref().map(|i| i.0), + i.payment_method.clone(), + i.payment_method_type.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/payment_intents/metrics/sessionized_metrics.rs b/crates/analytics/src/payment_intents/metrics/sessionized_metrics.rs new file mode 100644 index 000000000000..fcd9a2b8adfd --- /dev/null +++ b/crates/analytics/src/payment_intents/metrics/sessionized_metrics.rs @@ -0,0 +1,17 @@ +mod payment_intent_count; +mod payment_processed_amount; +mod payments_distribution; +mod payments_success_rate; +mod smart_retried_amount; +mod successful_smart_retries; +mod total_smart_retries; + +pub(super) use payment_intent_count::PaymentIntentCount; +pub(super) use payment_processed_amount::PaymentProcessedAmount; +pub(super) use payments_distribution::PaymentsDistribution; +pub(super) use payments_success_rate::PaymentsSuccessRate; +pub(super) use smart_retried_amount::SmartRetriedAmount; +pub(super) use successful_smart_retries::SuccessfulSmartRetries; +pub(super) use total_smart_retries::TotalSmartRetries; + +pub use super::{PaymentIntentMetric, PaymentIntentMetricAnalytics, PaymentIntentMetricRow}; diff --git a/crates/analytics/src/payment_intents/metrics/sessionized_metrics/payment_intent_count.rs b/crates/analytics/src/payment_intents/metrics/sessionized_metrics/payment_intent_count.rs new file mode 100644 index 000000000000..3f0f3bc3bc8a --- /dev/null +++ b/crates/analytics/src/payment_intents/metrics/sessionized_metrics/payment_intent_count.rs @@ -0,0 +1,133 @@ +use std::collections::HashSet; + +use api_models::analytics::{ + payment_intents::{ + PaymentIntentDimensions, PaymentIntentFilters, PaymentIntentMetricsBucketIdentifier, + }, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::PaymentIntentMetricRow; +use crate::{ + enums::AuthInfo, + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(crate) struct PaymentIntentCount; + +#[async_trait::async_trait] +impl super::PaymentIntentMetric for PaymentIntentCount +where + T: AnalyticsDataSource + super::PaymentIntentMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[PaymentIntentDimensions], + auth: &AuthInfo, + filters: &PaymentIntentFilters, + granularity: Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> + { + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::PaymentIntentSessionized); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + + if let Some(granularity) = granularity { + granularity + .set_group_by_clause(&mut query_builder) + .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(( + PaymentIntentMetricsBucketIdentifier::new( + i.status.as_ref().map(|i| i.0), + i.currency.as_ref().map(|i| i.0), + i.profile_id.clone(), + i.connector.clone(), + i.authentication_type.as_ref().map(|i| i.0), + i.payment_method.clone(), + i.payment_method_type.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/payment_intents/metrics/sessionized_metrics/payment_processed_amount.rs b/crates/analytics/src/payment_intents/metrics/sessionized_metrics/payment_processed_amount.rs new file mode 100644 index 000000000000..11b630a55cc1 --- /dev/null +++ b/crates/analytics/src/payment_intents/metrics/sessionized_metrics/payment_processed_amount.rs @@ -0,0 +1,163 @@ +use std::collections::HashSet; + +use api_models::analytics::{ + payment_intents::{ + PaymentIntentDimensions, PaymentIntentFilters, PaymentIntentMetricsBucketIdentifier, + }, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use diesel_models::enums as storage_enums; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::PaymentIntentMetricRow; +use crate::{ + enums::AuthInfo, + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(crate) struct PaymentProcessedAmount; + +#[async_trait::async_trait] +impl super::PaymentIntentMetric for PaymentProcessedAmount +where + T: AnalyticsDataSource + super::PaymentIntentMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[PaymentIntentDimensions], + auth: &AuthInfo, + filters: &PaymentIntentFilters, + granularity: Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> + { + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::PaymentIntentSessionized); + + let mut dimensions = dimensions.to_vec(); + + dimensions.push(PaymentIntentDimensions::PaymentIntentStatus); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + + query_builder + .add_select_column("(attempt_count = 1) as first_attempt") + .switch()?; + query_builder.add_select_column("currency").switch()?; + query_builder + .add_select_column(Aggregate::Sum { + field: "amount", + alias: Some("total"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + + query_builder + .add_group_by_clause("first_attempt") + .attach_printable("Error grouping by first_attempt") + .switch()?; + query_builder + .add_group_by_clause("currency") + .attach_printable("Error grouping by currency") + .switch()?; + if let Some(granularity) = granularity { + granularity + .set_group_by_clause(&mut query_builder) + .attach_printable("Error adding granularity") + .switch()?; + } + + query_builder + .add_filter_clause( + PaymentIntentDimensions::PaymentIntentStatus, + storage_enums::IntentStatus::Succeeded, + ) + .switch()?; + + query_builder + .execute_query::(pool) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(|i| { + Ok(( + PaymentIntentMetricsBucketIdentifier::new( + None, + i.currency.as_ref().map(|i| i.0), + i.profile_id.clone(), + i.connector.clone(), + i.authentication_type.as_ref().map(|i| i.0), + i.payment_method.clone(), + i.payment_method_type.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/payment_intents/metrics/sessionized_metrics/payments_distribution.rs b/crates/analytics/src/payment_intents/metrics/sessionized_metrics/payments_distribution.rs new file mode 100644 index 000000000000..dbb6d33e907e --- /dev/null +++ b/crates/analytics/src/payment_intents/metrics/sessionized_metrics/payments_distribution.rs @@ -0,0 +1,145 @@ +use std::collections::HashSet; + +use api_models::analytics::{ + payment_intents::{ + PaymentIntentDimensions, PaymentIntentFilters, PaymentIntentMetricsBucketIdentifier, + }, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::PaymentIntentMetricRow; +use crate::{ + enums::AuthInfo, + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(crate) struct PaymentsDistribution; + +#[async_trait::async_trait] +impl super::PaymentIntentMetric for PaymentsDistribution +where + T: AnalyticsDataSource + super::PaymentIntentMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[PaymentIntentDimensions], + auth: &AuthInfo, + filters: &PaymentIntentFilters, + granularity: Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> + { + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::PaymentIntentSessionized); + + let mut dimensions = dimensions.to_vec(); + + dimensions.push(PaymentIntentDimensions::PaymentIntentStatus); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + + query_builder + .add_select_column("(attempt_count = 1) as first_attempt") + .switch()?; + + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + query_builder + .add_group_by_clause("first_attempt") + .attach_printable("Error grouping by first_attempt") + .switch()?; + if let Some(granularity) = granularity { + granularity + .set_group_by_clause(&mut query_builder) + .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(( + PaymentIntentMetricsBucketIdentifier::new( + None, + i.currency.as_ref().map(|i| i.0), + i.profile_id.clone(), + i.connector.clone(), + i.authentication_type.as_ref().map(|i| i.0), + i.payment_method.clone(), + i.payment_method_type.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/payment_intents/metrics/sessionized_metrics/payments_success_rate.rs b/crates/analytics/src/payment_intents/metrics/sessionized_metrics/payments_success_rate.rs new file mode 100644 index 000000000000..178d9c1d816b --- /dev/null +++ b/crates/analytics/src/payment_intents/metrics/sessionized_metrics/payments_success_rate.rs @@ -0,0 +1,146 @@ +use std::collections::HashSet; + +use api_models::analytics::{ + payment_intents::{ + PaymentIntentDimensions, PaymentIntentFilters, PaymentIntentMetricsBucketIdentifier, + }, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::PaymentIntentMetricRow; +use crate::{ + enums::AuthInfo, + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(crate) struct PaymentsSuccessRate; + +#[async_trait::async_trait] +impl super::PaymentIntentMetric for PaymentsSuccessRate +where + T: AnalyticsDataSource + super::PaymentIntentMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[PaymentIntentDimensions], + auth: &AuthInfo, + filters: &PaymentIntentFilters, + granularity: Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> + { + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::PaymentIntentSessionized); + + let mut dimensions = dimensions.to_vec(); + + dimensions.push(PaymentIntentDimensions::PaymentIntentStatus); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + + query_builder + .add_select_column("(attempt_count = 1) as first_attempt".to_string()) + .switch()?; + + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + + query_builder + .add_group_by_clause("first_attempt") + .attach_printable("Error grouping by first_attempt") + .switch()?; + + if let Some(granularity) = granularity { + granularity + .set_group_by_clause(&mut query_builder) + .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(( + PaymentIntentMetricsBucketIdentifier::new( + None, + i.currency.as_ref().map(|i| i.0), + i.profile_id.clone(), + i.connector.clone(), + i.authentication_type.as_ref().map(|i| i.0), + i.payment_method.clone(), + i.payment_method_type.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/payment_intents/metrics/sessionized_metrics/smart_retried_amount.rs b/crates/analytics/src/payment_intents/metrics/sessionized_metrics/smart_retried_amount.rs new file mode 100644 index 000000000000..40e41253d777 --- /dev/null +++ b/crates/analytics/src/payment_intents/metrics/sessionized_metrics/smart_retried_amount.rs @@ -0,0 +1,158 @@ +use std::collections::HashSet; + +use api_models::{ + analytics::{ + payment_intents::{ + PaymentIntentDimensions, PaymentIntentFilters, PaymentIntentMetricsBucketIdentifier, + }, + Granularity, TimeRange, + }, + enums::IntentStatus, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::PaymentIntentMetricRow; +use crate::{ + enums::AuthInfo, + query::{ + Aggregate, FilterTypes, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, + Window, + }, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(crate) struct SmartRetriedAmount; + +#[async_trait::async_trait] +impl super::PaymentIntentMetric for SmartRetriedAmount +where + T: AnalyticsDataSource + super::PaymentIntentMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[PaymentIntentDimensions], + auth: &AuthInfo, + filters: &PaymentIntentFilters, + granularity: Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> + { + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::PaymentIntentSessionized); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + query_builder + .add_select_column(Aggregate::Sum { + field: "amount", + alias: Some("total"), + }) + .switch()?; + + query_builder + .add_select_column("(attempt_count = 1) as first_attempt") + .switch()?; + + query_builder.add_select_column("currency").switch()?; + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + query_builder + .add_custom_filter_clause("attempt_count", "1", FilterTypes::Gt) + .switch()?; + query_builder + .add_custom_filter_clause("status", IntentStatus::Succeeded, FilterTypes::Equal) + .switch()?; + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + + query_builder + .add_group_by_clause("first_attempt") + .attach_printable("Error grouping by first_attempt") + .switch()?; + query_builder + .add_group_by_clause("currency") + .attach_printable("Error grouping by currency") + .switch()?; + if let Some(granularity) = granularity { + granularity + .set_group_by_clause(&mut query_builder) + .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(( + PaymentIntentMetricsBucketIdentifier::new( + i.status.as_ref().map(|i| i.0), + i.currency.as_ref().map(|i| i.0), + i.profile_id.clone(), + i.connector.clone(), + i.authentication_type.as_ref().map(|i| i.0), + i.payment_method.clone(), + i.payment_method_type.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/payment_intents/metrics/sessionized_metrics/successful_smart_retries.rs b/crates/analytics/src/payment_intents/metrics/sessionized_metrics/successful_smart_retries.rs new file mode 100644 index 000000000000..84f6d31dfc6f --- /dev/null +++ b/crates/analytics/src/payment_intents/metrics/sessionized_metrics/successful_smart_retries.rs @@ -0,0 +1,143 @@ +use std::collections::HashSet; + +use api_models::{ + analytics::{ + payment_intents::{ + PaymentIntentDimensions, PaymentIntentFilters, PaymentIntentMetricsBucketIdentifier, + }, + Granularity, TimeRange, + }, + enums::IntentStatus, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::PaymentIntentMetricRow; +use crate::{ + enums::AuthInfo, + query::{ + Aggregate, FilterTypes, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, + Window, + }, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(crate) struct SuccessfulSmartRetries; + +#[async_trait::async_trait] +impl super::PaymentIntentMetric for SuccessfulSmartRetries +where + T: AnalyticsDataSource + super::PaymentIntentMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[PaymentIntentDimensions], + auth: &AuthInfo, + filters: &PaymentIntentFilters, + granularity: Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> + { + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::PaymentIntentSessionized); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + query_builder + .add_custom_filter_clause("attempt_count", "1", FilterTypes::Gt) + .switch()?; + query_builder + .add_custom_filter_clause("status", IntentStatus::Succeeded, FilterTypes::Equal) + .switch()?; + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + if let Some(granularity) = granularity { + granularity + .set_group_by_clause(&mut query_builder) + .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(( + PaymentIntentMetricsBucketIdentifier::new( + i.status.as_ref().map(|i| i.0), + i.currency.as_ref().map(|i| i.0), + i.profile_id.clone(), + i.connector.clone(), + i.authentication_type.as_ref().map(|i| i.0), + i.payment_method.clone(), + i.payment_method_type.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/payment_intents/metrics/sessionized_metrics/total_smart_retries.rs b/crates/analytics/src/payment_intents/metrics/sessionized_metrics/total_smart_retries.rs new file mode 100644 index 000000000000..8cc4e75b3ba7 --- /dev/null +++ b/crates/analytics/src/payment_intents/metrics/sessionized_metrics/total_smart_retries.rs @@ -0,0 +1,138 @@ +use std::collections::HashSet; + +use api_models::analytics::{ + payment_intents::{ + PaymentIntentDimensions, PaymentIntentFilters, PaymentIntentMetricsBucketIdentifier, + }, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::PaymentIntentMetricRow; +use crate::{ + enums::AuthInfo, + query::{ + Aggregate, FilterTypes, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, + Window, + }, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(crate) struct TotalSmartRetries; + +#[async_trait::async_trait] +impl super::PaymentIntentMetric for TotalSmartRetries +where + T: AnalyticsDataSource + super::PaymentIntentMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[PaymentIntentDimensions], + auth: &AuthInfo, + filters: &PaymentIntentFilters, + granularity: Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> + { + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::PaymentIntentSessionized); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + query_builder + .add_custom_filter_clause("attempt_count", "1", FilterTypes::Gt) + .switch()?; + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + + if let Some(granularity) = granularity { + granularity + .set_group_by_clause(&mut query_builder) + .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(( + PaymentIntentMetricsBucketIdentifier::new( + i.status.as_ref().map(|i| i.0), + i.currency.as_ref().map(|i| i.0), + i.profile_id.clone(), + i.connector.clone(), + i.authentication_type.as_ref().map(|i| i.0), + i.payment_method.clone(), + i.payment_method_type.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/payment_intents/metrics/smart_retried_amount.rs b/crates/analytics/src/payment_intents/metrics/smart_retried_amount.rs index e1df9fe50d35..d51cb4cdf739 100644 --- a/crates/analytics/src/payment_intents/metrics/smart_retried_amount.rs +++ b/crates/analytics/src/payment_intents/metrics/smart_retried_amount.rs @@ -41,7 +41,7 @@ where dimensions: &[PaymentIntentDimensions], auth: &AuthInfo, filters: &PaymentIntentFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> @@ -58,6 +58,8 @@ where alias: Some("total"), }) .switch()?; + + query_builder.add_select_column("currency").switch()?; query_builder .add_select_column(Aggregate::Min { field: "created_at", @@ -93,7 +95,11 @@ where .switch()?; } - if let Some(granularity) = granularity.as_ref() { + query_builder + .add_group_by_clause("currency") + .attach_printable("Error grouping by currency") + .switch()?; + if let Some(granularity) = granularity { granularity .set_group_by_clause(&mut query_builder) .attach_printable("Error adding granularity") @@ -112,6 +118,15 @@ where i.status.as_ref().map(|i| i.0), i.currency.as_ref().map(|i| i.0), i.profile_id.clone(), + i.connector.clone(), + i.authentication_type.as_ref().map(|i| i.0), + i.payment_method.clone(), + i.payment_method_type.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.clone(), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/payment_intents/metrics/successful_smart_retries.rs b/crates/analytics/src/payment_intents/metrics/successful_smart_retries.rs index 4fe5f3a26f51..524b3ff29393 100644 --- a/crates/analytics/src/payment_intents/metrics/successful_smart_retries.rs +++ b/crates/analytics/src/payment_intents/metrics/successful_smart_retries.rs @@ -41,7 +41,7 @@ where dimensions: &[PaymentIntentDimensions], auth: &AuthInfo, filters: &PaymentIntentFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> @@ -92,7 +92,7 @@ where .attach_printable("Error grouping by dimensions") .switch()?; } - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { granularity .set_group_by_clause(&mut query_builder) .attach_printable("Error adding granularity") @@ -111,6 +111,15 @@ where i.status.as_ref().map(|i| i.0), i.currency.as_ref().map(|i| i.0), i.profile_id.clone(), + i.connector.clone(), + i.authentication_type.as_ref().map(|i| i.0), + i.payment_method.clone(), + i.payment_method_type.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.clone(), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/payment_intents/metrics/total_smart_retries.rs b/crates/analytics/src/payment_intents/metrics/total_smart_retries.rs index e98efa9f6abc..8eb561d38d5d 100644 --- a/crates/analytics/src/payment_intents/metrics/total_smart_retries.rs +++ b/crates/analytics/src/payment_intents/metrics/total_smart_retries.rs @@ -38,7 +38,7 @@ where dimensions: &[PaymentIntentDimensions], auth: &AuthInfo, filters: &PaymentIntentFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> @@ -87,7 +87,7 @@ where .switch()?; } - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { granularity .set_group_by_clause(&mut query_builder) .attach_printable("Error adding granularity") @@ -106,6 +106,15 @@ where i.status.as_ref().map(|i| i.0), i.currency.as_ref().map(|i| i.0), i.profile_id.clone(), + i.connector.clone(), + i.authentication_type.as_ref().map(|i| i.0), + i.payment_method.clone(), + i.payment_method_type.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.clone(), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/payment_intents/sankey.rs b/crates/analytics/src/payment_intents/sankey.rs new file mode 100644 index 000000000000..626dcef27443 --- /dev/null +++ b/crates/analytics/src/payment_intents/sankey.rs @@ -0,0 +1,149 @@ +use common_enums::enums; +use common_utils::{ + errors::ParsingError, + types::{authentication::AuthInfo, TimeRange}, +}; +use error_stack::ResultExt; +use router_env::logger; + +use crate::{ + clickhouse::ClickhouseClient, + query::{Aggregate, QueryBuilder, QueryFilter}, + types::{AnalyticsCollection, DBEnumWrapper, MetricsError, MetricsResult}, +}; + +#[derive( + Clone, + Copy, + Debug, + Default, + Eq, + Hash, + PartialEq, + serde::Deserialize, + serde::Serialize, + strum::Display, + strum::EnumIter, + strum::EnumString, +)] +#[serde(rename_all = "snake_case")] +pub enum SessionizerRefundStatus { + FullRefunded, + #[default] + NotRefunded, + PartialRefunded, +} + +#[derive( + Clone, + Copy, + Debug, + Default, + Eq, + Hash, + PartialEq, + serde::Deserialize, + serde::Serialize, + strum::Display, + strum::EnumIter, + strum::EnumString, +)] +#[serde(rename_all = "snake_case")] +pub enum SessionizerDisputeStatus { + DisputePresent, + #[default] + NotDisputed, +} + +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct SankeyRow { + pub count: i64, + pub status: DBEnumWrapper, + #[serde(default)] + pub refunds_status: Option>, + #[serde(default)] + pub dispute_status: Option>, + pub first_attempt: i64, +} + +impl TryInto for serde_json::Value { + type Error = error_stack::Report; + + fn try_into(self) -> Result { + logger::debug!("Parsing SankeyRow from {:?}", self); + serde_json::from_value(self).change_context(ParsingError::StructParseFailure( + "Failed to parse Sankey in clickhouse results", + )) + } +} + +pub async fn get_sankey_data( + clickhouse_client: &ClickhouseClient, + auth: &AuthInfo, + time_range: &TimeRange, +) -> MetricsResult> { + let mut query_builder = + QueryBuilder::::new(AnalyticsCollection::PaymentIntentSessionized); + query_builder + .add_select_column(Aggregate::::Count { + field: None, + alias: Some("count"), + }) + .change_context(MetricsError::QueryBuildingError)?; + + query_builder + .add_select_column("status") + .attach_printable("Error adding select clause") + .change_context(MetricsError::QueryBuildingError)?; + + query_builder + .add_select_column("refunds_status") + .attach_printable("Error adding select clause") + .change_context(MetricsError::QueryBuildingError)?; + + query_builder + .add_select_column("dispute_status") + .attach_printable("Error adding select clause") + .change_context(MetricsError::QueryBuildingError)?; + + query_builder + .add_select_column("(attempt_count = 1) as first_attempt") + .attach_printable("Error adding select clause") + .change_context(MetricsError::QueryBuildingError)?; + + auth.set_filter_clause(&mut query_builder) + .change_context(MetricsError::QueryBuildingError)?; + + time_range + .set_filter_clause(&mut query_builder) + .change_context(MetricsError::QueryBuildingError)?; + + query_builder + .add_group_by_clause("status") + .attach_printable("Error adding group by clause") + .change_context(MetricsError::QueryBuildingError)?; + + query_builder + .add_group_by_clause("refunds_status") + .attach_printable("Error adding group by clause") + .change_context(MetricsError::QueryBuildingError)?; + + query_builder + .add_group_by_clause("dispute_status") + .attach_printable("Error adding group by clause") + .change_context(MetricsError::QueryBuildingError)?; + + query_builder + .add_group_by_clause("first_attempt") + .attach_printable("Error adding group by clause") + .change_context(MetricsError::QueryBuildingError)?; + + query_builder + .execute_query::(clickhouse_client) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(Ok) + .collect() +} diff --git a/crates/analytics/src/payment_intents/types.rs b/crates/analytics/src/payment_intents/types.rs index 03f2a196c20e..a27ef2d840df 100644 --- a/crates/analytics/src/payment_intents/types.rs +++ b/crates/analytics/src/payment_intents/types.rs @@ -30,6 +30,68 @@ where .add_filter_in_range_clause(PaymentIntentDimensions::ProfileId, &self.profile_id) .attach_printable("Error adding profile id filter")?; } + if !self.connector.is_empty() { + builder + .add_filter_in_range_clause(PaymentIntentDimensions::Connector, &self.connector) + .attach_printable("Error adding connector filter")?; + } + if !self.auth_type.is_empty() { + builder + .add_filter_in_range_clause(PaymentIntentDimensions::AuthType, &self.auth_type) + .attach_printable("Error adding auth type filter")?; + } + if !self.payment_method.is_empty() { + builder + .add_filter_in_range_clause( + PaymentIntentDimensions::PaymentMethod, + &self.payment_method, + ) + .attach_printable("Error adding payment method filter")?; + } + if !self.payment_method_type.is_empty() { + builder + .add_filter_in_range_clause( + PaymentIntentDimensions::PaymentMethodType, + &self.payment_method_type, + ) + .attach_printable("Error adding payment method type filter")?; + } + if !self.card_network.is_empty() { + builder + .add_filter_in_range_clause( + PaymentIntentDimensions::CardNetwork, + &self.card_network, + ) + .attach_printable("Error adding card network filter")?; + } + if !self.merchant_id.is_empty() { + builder + .add_filter_in_range_clause(PaymentIntentDimensions::MerchantId, &self.merchant_id) + .attach_printable("Error adding merchant id filter")?; + } + if !self.card_last_4.is_empty() { + builder + .add_filter_in_range_clause(PaymentIntentDimensions::CardLast4, &self.card_last_4) + .attach_printable("Error adding card last 4 filter")?; + } + if !self.card_issuer.is_empty() { + builder + .add_filter_in_range_clause(PaymentIntentDimensions::CardIssuer, &self.card_issuer) + .attach_printable("Error adding card issuer filter")?; + } + if !self.error_reason.is_empty() { + builder + .add_filter_in_range_clause( + PaymentIntentDimensions::ErrorReason, + &self.error_reason, + ) + .attach_printable("Error adding error reason filter")?; + } + if !self.customer_id.is_empty() { + builder + .add_filter_in_range_clause("customer_id", &self.customer_id) + .attach_printable("Error adding customer id filter")?; + } Ok(()) } } diff --git a/crates/analytics/src/payments/accumulator.rs b/crates/analytics/src/payments/accumulator.rs index efc8aaf69832..20ccc634068d 100644 --- a/crates/analytics/src/payments/accumulator.rs +++ b/crates/analytics/src/payments/accumulator.rs @@ -10,12 +10,14 @@ pub struct PaymentMetricsAccumulator { pub payment_success_rate: SuccessRateAccumulator, pub payment_count: CountAccumulator, pub payment_success: CountAccumulator, - pub processed_amount: SumAccumulator, + pub processed_amount: ProcessedAmountAccumulator, pub avg_ticket_size: AverageAccumulator, pub payment_error_message: ErrorDistributionAccumulator, pub retries_count: CountAccumulator, - pub retries_amount_processed: SumAccumulator, + pub retries_amount_processed: RetriesAmountAccumulator, pub connector_success_rate: SuccessRateAccumulator, + pub payments_distribution: PaymentsDistributionAccumulator, + pub failure_reasons_distribution: FailureReasonsDistributionAccumulator, } #[derive(Debug, Default)] @@ -30,6 +32,12 @@ pub struct ErrorDistributionAccumulator { pub error_vec: Vec, } +#[derive(Debug, Default)] +pub struct FailureReasonsDistributionAccumulator { + pub count: u64, + pub count_without_retries: u64, +} + #[derive(Debug, Default)] pub struct SuccessRateAccumulator { pub success: i64, @@ -43,9 +51,11 @@ pub struct CountAccumulator { } #[derive(Debug, Default)] -#[repr(transparent)] -pub struct SumAccumulator { - pub total: Option, +pub struct ProcessedAmountAccumulator { + pub count_with_retries: Option, + pub total_with_retries: Option, + pub count_without_retries: Option, + pub total_without_retries: Option, } #[derive(Debug, Default)] @@ -54,6 +64,25 @@ pub struct AverageAccumulator { pub count: u32, } +#[derive(Debug, Default)] +#[repr(transparent)] +pub struct RetriesAmountAccumulator { + pub total: Option, +} + +#[derive(Debug, Default)] +pub struct PaymentsDistributionAccumulator { + pub success: u32, + pub failed: u32, + pub total: u32, + pub success_without_retries: u32, + pub success_with_only_retries: u32, + pub failed_without_retries: u32, + pub failed_with_only_retries: u32, + pub total_without_retries: u32, + pub total_with_only_retries: u32, +} + pub trait PaymentMetricAccumulator { type MetricOutput; @@ -107,6 +136,29 @@ impl PaymentDistributionAccumulator for ErrorDistributionAccumulator { } } +impl PaymentMetricAccumulator for FailureReasonsDistributionAccumulator { + type MetricOutput = (Option, Option); + + fn add_metrics_bucket(&mut self, metrics: &PaymentMetricRow) { + if let Some(count) = metrics.count { + if let Ok(count_u64) = u64::try_from(count) { + self.count += count_u64; + } + } + if metrics.first_attempt.unwrap_or(false) { + if let Some(count) = metrics.count { + if let Ok(count_u64) = u64::try_from(count) { + self.count_without_retries += count_u64; + } + } + } + } + + fn collect(self) -> Self::MetricOutput { + (Some(self.count), Some(self.count_without_retries)) + } +} + impl PaymentMetricAccumulator for SuccessRateAccumulator { type MetricOutput = Option; @@ -131,6 +183,118 @@ impl PaymentMetricAccumulator for SuccessRateAccumulator { } } +impl PaymentMetricAccumulator for PaymentsDistributionAccumulator { + type MetricOutput = ( + Option, + Option, + Option, + Option, + Option, + Option, + ); + + fn add_metrics_bucket(&mut self, metrics: &PaymentMetricRow) { + if let Some(ref status) = metrics.status { + if status.as_ref() == &storage_enums::AttemptStatus::Charged { + if let Some(success) = metrics + .count + .and_then(|success| u32::try_from(success).ok()) + { + self.success += success; + if metrics.first_attempt.unwrap_or(false) { + self.success_without_retries += success; + } else { + self.success_with_only_retries += success; + } + } + } + if status.as_ref() == &storage_enums::AttemptStatus::Failure { + if let Some(failed) = metrics.count.and_then(|failed| u32::try_from(failed).ok()) { + self.failed += failed; + if metrics.first_attempt.unwrap_or(false) { + self.failed_without_retries += failed; + } else { + self.failed_with_only_retries += failed; + } + } + } + if status.as_ref() != &storage_enums::AttemptStatus::AuthenticationFailed + && status.as_ref() != &storage_enums::AttemptStatus::PaymentMethodAwaited + && status.as_ref() != &storage_enums::AttemptStatus::DeviceDataCollectionPending + && status.as_ref() != &storage_enums::AttemptStatus::ConfirmationAwaited + && status.as_ref() != &storage_enums::AttemptStatus::Unresolved + { + if let Some(total) = metrics.count.and_then(|total| u32::try_from(total).ok()) { + self.total += total; + if metrics.first_attempt.unwrap_or(false) { + self.total_without_retries += total; + } else { + self.total_with_only_retries += total; + } + } + } + } + } + + fn collect(self) -> Self::MetricOutput { + if self.total == 0 { + (None, None, None, None, None, None) + } else { + let success = Some(self.success); + let success_without_retries = Some(self.success_without_retries); + let success_with_only_retries = Some(self.success_with_only_retries); + let failed = Some(self.failed); + let failed_with_only_retries = Some(self.failed_with_only_retries); + let failed_without_retries = Some(self.failed_without_retries); + let total = Some(self.total); + let total_without_retries = Some(self.total_without_retries); + let total_with_only_retries = Some(self.total_with_only_retries); + + let success_rate = match (success, total) { + (Some(s), Some(t)) if t > 0 => Some(f64::from(s) * 100.0 / f64::from(t)), + _ => None, + }; + + let success_rate_without_retries = + match (success_without_retries, total_without_retries) { + (Some(s), Some(t)) if t > 0 => Some(f64::from(s) * 100.0 / f64::from(t)), + _ => None, + }; + + let success_rate_with_only_retries = + match (success_with_only_retries, total_with_only_retries) { + (Some(s), Some(t)) if t > 0 => Some(f64::from(s) * 100.0 / f64::from(t)), + _ => None, + }; + + let failed_rate = match (failed, total) { + (Some(s), Some(t)) if t > 0 => Some(f64::from(s) * 100.0 / f64::from(t)), + _ => None, + }; + + let failed_rate_without_retries = match (failed_without_retries, total_without_retries) + { + (Some(s), Some(t)) if t > 0 => Some(f64::from(s) * 100.0 / f64::from(t)), + _ => None, + }; + + let failed_rate_with_only_retries = + match (failed_with_only_retries, total_with_only_retries) { + (Some(s), Some(t)) if t > 0 => Some(f64::from(s) * 100.0 / f64::from(t)), + _ => None, + }; + ( + success_rate, + success_rate_without_retries, + success_rate_with_only_retries, + failed_rate, + failed_rate_without_retries, + failed_rate_with_only_retries, + ) + } + } +} + impl PaymentMetricAccumulator for CountAccumulator { type MetricOutput = Option; #[inline] @@ -147,9 +311,72 @@ impl PaymentMetricAccumulator for CountAccumulator { } } -impl PaymentMetricAccumulator for SumAccumulator { - type MetricOutput = Option; +impl PaymentMetricAccumulator for ProcessedAmountAccumulator { + type MetricOutput = ( + Option, + Option, + Option, + Option, + Option, + Option, + ); + #[inline] + fn add_metrics_bucket(&mut self, metrics: &PaymentMetricRow) { + self.total_with_retries = match ( + self.total_with_retries, + metrics.total.as_ref().and_then(ToPrimitive::to_i64), + ) { + (None, None) => None, + (None, i @ Some(_)) | (i @ Some(_), None) => i, + (Some(a), Some(b)) => Some(a + b), + }; + + self.count_with_retries = match (self.count_with_retries, metrics.count) { + (None, None) => None, + (None, i @ Some(_)) | (i @ Some(_), None) => i, + (Some(a), Some(b)) => Some(a + b), + }; + + if metrics.first_attempt.unwrap_or(false) { + self.total_without_retries = match ( + self.total_without_retries, + metrics.total.as_ref().and_then(ToPrimitive::to_i64), + ) { + (None, None) => None, + (None, i @ Some(_)) | (i @ Some(_), None) => i, + (Some(a), Some(b)) => Some(a + b), + }; + + self.count_without_retries = match (self.count_without_retries, metrics.count) { + (None, None) => None, + (None, i @ Some(_)) | (i @ Some(_), None) => i, + (Some(a), Some(b)) => Some(a + b), + }; + } + } #[inline] + fn collect(self) -> Self::MetricOutput { + let total_with_retries = u64::try_from(self.total_with_retries.unwrap_or(0)).ok(); + let count_with_retries = self.count_with_retries.and_then(|i| u64::try_from(i).ok()); + + let total_without_retries = u64::try_from(self.total_without_retries.unwrap_or(0)).ok(); + let count_without_retries = self + .count_without_retries + .and_then(|i| u64::try_from(i).ok()); + + ( + total_with_retries, + count_with_retries, + total_without_retries, + count_without_retries, + Some(0), + Some(0), + ) + } +} + +impl PaymentMetricAccumulator for RetriesAmountAccumulator { + type MetricOutput = Option; fn add_metrics_bucket(&mut self, metrics: &PaymentMetricRow) { self.total = match ( self.total, @@ -158,7 +385,7 @@ impl PaymentMetricAccumulator for SumAccumulator { (None, None) => None, (None, i @ Some(_)) | (i @ Some(_), None) => i, (Some(a), Some(b)) => Some(a + b), - } + }; } #[inline] fn collect(self) -> Self::MetricOutput { @@ -195,16 +422,47 @@ impl PaymentMetricAccumulator for AverageAccumulator { impl PaymentMetricsAccumulator { pub fn collect(self) -> PaymentMetricsBucketValue { + let ( + payment_processed_amount, + payment_processed_count, + payment_processed_amount_without_smart_retries, + payment_processed_count_without_smart_retries, + payment_processed_amount_in_usd, + payment_processed_amount_without_smart_retries_usd, + ) = self.processed_amount.collect(); + let ( + payments_success_rate_distribution, + payments_success_rate_distribution_without_smart_retries, + payments_success_rate_distribution_with_only_retries, + payments_failure_rate_distribution, + payments_failure_rate_distribution_without_smart_retries, + payments_failure_rate_distribution_with_only_retries, + ) = self.payments_distribution.collect(); + let (failure_reason_count, failure_reason_count_without_smart_retries) = + self.failure_reasons_distribution.collect(); PaymentMetricsBucketValue { payment_success_rate: self.payment_success_rate.collect(), payment_count: self.payment_count.collect(), payment_success_count: self.payment_success.collect(), - payment_processed_amount: self.processed_amount.collect(), + payment_processed_amount, + payment_processed_count, + payment_processed_amount_without_smart_retries, + payment_processed_count_without_smart_retries, avg_ticket_size: self.avg_ticket_size.collect(), payment_error_message: self.payment_error_message.collect(), retries_count: self.retries_count.collect(), retries_amount_processed: self.retries_amount_processed.collect(), connector_success_rate: self.connector_success_rate.collect(), + payments_success_rate_distribution, + payments_success_rate_distribution_without_smart_retries, + payments_success_rate_distribution_with_only_retries, + payments_failure_rate_distribution, + payments_failure_rate_distribution_without_smart_retries, + payments_failure_rate_distribution_with_only_retries, + failure_reason_count, + failure_reason_count_without_smart_retries, + payment_processed_amount_in_usd, + payment_processed_amount_without_smart_retries_usd, } } } diff --git a/crates/analytics/src/payments/core.rs b/crates/analytics/src/payments/core.rs index f41e17194500..7291d2f1fcc7 100644 --- a/crates/analytics/src/payments/core.rs +++ b/crates/analytics/src/payments/core.rs @@ -6,14 +6,16 @@ use api_models::analytics::{ MetricsBucketResponse, PaymentDimensions, PaymentDistributions, PaymentMetrics, PaymentMetricsBucketIdentifier, }, - AnalyticsMetadata, FilterValue, GetPaymentFiltersRequest, GetPaymentMetricRequest, - MetricsResponse, PaymentFiltersResponse, + FilterValue, GetPaymentFiltersRequest, GetPaymentMetricRequest, PaymentFiltersResponse, + PaymentsAnalyticsMetadata, PaymentsMetricsResponse, }; +use bigdecimal::ToPrimitive; +use common_enums::Currency; use common_utils::errors::CustomResult; +use currency_conversion::{conversion::convert, types::ExchangeRates}; use error_stack::ResultExt; use router_env::{ instrument, logger, - metrics::add_attributes, tracing::{self, Instrument}, }; @@ -46,9 +48,10 @@ pub enum TaskType { #[instrument(skip_all)] pub async fn get_metrics( pool: &AnalyticsProvider, + ex_rates: &Option, auth: &AuthInfo, req: GetPaymentMetricRequest, -) -> AnalyticsResult> { +) -> AnalyticsResult> { let mut metrics_accumulator: HashMap< PaymentMetricsBucketIdentifier, PaymentMetricsAccumulator, @@ -74,7 +77,7 @@ pub async fn get_metrics( &req.group_by_names.clone(), &auth_scoped, &req.filters, - &req.time_series.map(|t| t.granularity), + req.time_series.map(|t| t.granularity), &req.time_range, ) .await @@ -102,7 +105,7 @@ pub async fn get_metrics( &req.group_by_names.clone(), &auth_scoped, &req.filters, - &req.time_series.map(|t| t.granularity), + req.time_series.map(|t| t.granularity), &req.time_range, ) .await @@ -122,14 +125,14 @@ pub async fn get_metrics( match task_type { TaskType::MetricTask(metric, data) => { let data = data?; - let attributes = &add_attributes([ + let attributes = router_env::metric_attributes!( ("metric_type", metric.to_string()), ("source", pool.to_string()), - ]); + ); let value = u64::try_from(data.len()); if let Ok(val) = value { - metrics::BUCKETS_FETCHED.record(&metrics::CONTEXT, val, attributes); + metrics::BUCKETS_FETCHED.record(val, attributes); logger::debug!("Attributes: {:?}, Buckets fetched: {}", attributes, val); } @@ -137,32 +140,47 @@ pub async fn get_metrics( logger::debug!(bucket_id=?id, bucket_value=?value, "Bucket row for metric {metric}"); let metrics_builder = metrics_accumulator.entry(id).or_default(); match metric { - PaymentMetrics::PaymentSuccessRate => metrics_builder + PaymentMetrics::PaymentSuccessRate + | PaymentMetrics::SessionizedPaymentSuccessRate => metrics_builder .payment_success_rate .add_metrics_bucket(&value), - PaymentMetrics::PaymentCount => { + PaymentMetrics::PaymentCount | PaymentMetrics::SessionizedPaymentCount => { metrics_builder.payment_count.add_metrics_bucket(&value) } - PaymentMetrics::PaymentSuccessCount => { + PaymentMetrics::PaymentSuccessCount + | PaymentMetrics::SessionizedPaymentSuccessCount => { metrics_builder.payment_success.add_metrics_bucket(&value) } - PaymentMetrics::PaymentProcessedAmount => { + PaymentMetrics::PaymentProcessedAmount + | PaymentMetrics::SessionizedPaymentProcessedAmount => { metrics_builder.processed_amount.add_metrics_bucket(&value) } - PaymentMetrics::AvgTicketSize => { + PaymentMetrics::AvgTicketSize + | PaymentMetrics::SessionizedAvgTicketSize => { metrics_builder.avg_ticket_size.add_metrics_bucket(&value) } - PaymentMetrics::RetriesCount => { + PaymentMetrics::RetriesCount | PaymentMetrics::SessionizedRetriesCount => { metrics_builder.retries_count.add_metrics_bucket(&value); metrics_builder .retries_amount_processed .add_metrics_bucket(&value) } - PaymentMetrics::ConnectorSuccessRate => { + PaymentMetrics::ConnectorSuccessRate + | PaymentMetrics::SessionizedConnectorSuccessRate => { metrics_builder .connector_success_rate .add_metrics_bucket(&value); } + PaymentMetrics::PaymentsDistribution => { + metrics_builder + .payments_distribution + .add_metrics_bucket(&value); + } + PaymentMetrics::FailureReasons => { + metrics_builder + .failure_reasons_distribution + .add_metrics_bucket(&value); + } } } @@ -174,14 +192,14 @@ pub async fn get_metrics( } TaskType::DistributionTask(distribution, data) => { let data = data?; - let attributes = &add_attributes([ + let attributes = router_env::metric_attributes!( ("distribution_type", distribution.to_string()), ("source", pool.to_string()), - ]); + ); let value = u64::try_from(data.len()); if let Ok(val) = value { - metrics::BUCKETS_FETCHED.record(&metrics::CONTEXT, val, attributes); + metrics::BUCKETS_FETCHED.record(val, attributes); logger::debug!("Attributes: {:?}, Buckets fetched: {}", attributes, val); } @@ -203,19 +221,110 @@ pub async fn get_metrics( } } } - + let mut total_payment_processed_amount = 0; + let mut total_payment_processed_count = 0; + let mut total_payment_processed_amount_without_smart_retries = 0; + let mut total_payment_processed_count_without_smart_retries = 0; + let mut total_failure_reasons_count = 0; + let mut total_failure_reasons_count_without_smart_retries = 0; + let mut total_payment_processed_amount_in_usd = 0; + let mut total_payment_processed_amount_without_smart_retries_usd = 0; let query_data: Vec = metrics_accumulator .into_iter() - .map(|(id, val)| MetricsBucketResponse { - values: val.collect(), - dimensions: id, + .map(|(id, val)| { + let mut collected_values = val.collect(); + if let Some(amount) = collected_values.payment_processed_amount { + let amount_in_usd = if let Some(ex_rates) = ex_rates { + id.currency + .and_then(|currency| { + i64::try_from(amount) + .inspect_err(|e| logger::error!("Amount conversion error: {:?}", e)) + .ok() + .and_then(|amount_i64| { + convert(ex_rates, currency, Currency::USD, amount_i64) + .inspect_err(|e| { + logger::error!("Currency conversion error: {:?}", e) + }) + .ok() + }) + }) + .map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64()) + .unwrap_or_default() + } else { + None + }; + collected_values.payment_processed_amount_in_usd = amount_in_usd; + total_payment_processed_amount += amount; + total_payment_processed_amount_in_usd += amount_in_usd.unwrap_or(0); + } + if let Some(count) = collected_values.payment_processed_count { + total_payment_processed_count += count; + } + if let Some(amount) = collected_values.payment_processed_amount_without_smart_retries { + let amount_in_usd = if let Some(ex_rates) = ex_rates { + id.currency + .and_then(|currency| { + i64::try_from(amount) + .inspect_err(|e| logger::error!("Amount conversion error: {:?}", e)) + .ok() + .and_then(|amount_i64| { + convert(ex_rates, currency, Currency::USD, amount_i64) + .inspect_err(|e| { + logger::error!("Currency conversion error: {:?}", e) + }) + .ok() + }) + }) + .map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64()) + .unwrap_or_default() + } else { + None + }; + collected_values.payment_processed_amount_without_smart_retries_usd = amount_in_usd; + total_payment_processed_amount_without_smart_retries += amount; + total_payment_processed_amount_without_smart_retries_usd += + amount_in_usd.unwrap_or(0); + } + if let Some(count) = collected_values.payment_processed_count_without_smart_retries { + total_payment_processed_count_without_smart_retries += count; + } + if let Some(count) = collected_values.failure_reason_count { + total_failure_reasons_count += count; + } + if let Some(count) = collected_values.failure_reason_count_without_smart_retries { + total_failure_reasons_count_without_smart_retries += count; + } + MetricsBucketResponse { + values: collected_values, + dimensions: id, + } }) .collect(); - - Ok(MetricsResponse { + Ok(PaymentsMetricsResponse { query_data, - meta_data: [AnalyticsMetadata { - current_time_range: req.time_range, + meta_data: [PaymentsAnalyticsMetadata { + total_payment_processed_amount: Some(total_payment_processed_amount), + total_payment_processed_amount_in_usd: if ex_rates.is_some() { + Some(total_payment_processed_amount_in_usd) + } else { + None + }, + total_payment_processed_amount_without_smart_retries: Some( + total_payment_processed_amount_without_smart_retries, + ), + total_payment_processed_amount_without_smart_retries_usd: if ex_rates.is_some() { + Some(total_payment_processed_amount_without_smart_retries_usd) + } else { + None + }, + total_payment_processed_count: Some(total_payment_processed_count), + total_payment_processed_count_without_smart_retries: Some( + total_payment_processed_count_without_smart_retries, + ), + total_failure_reasons_count: Some(total_failure_reasons_count), + total_failure_reasons_count_without_smart_retries: Some( + total_failure_reasons_count_without_smart_retries, + ), }], }) } @@ -297,6 +406,10 @@ pub async fn get_filters( PaymentDimensions::ClientVersion => fil.client_version, PaymentDimensions::ProfileId => fil.profile_id, PaymentDimensions::CardNetwork => fil.card_network, + PaymentDimensions::MerchantId => fil.merchant_id, + PaymentDimensions::CardLast4 => fil.card_last_4, + PaymentDimensions::CardIssuer => fil.card_issuer, + PaymentDimensions::ErrorReason => fil.error_reason, }) .collect::>(); res.query_data.push(FilterValue { diff --git a/crates/analytics/src/payments/distribution.rs b/crates/analytics/src/payments/distribution.rs index 3bd8e5f78157..86a2f06c5f50 100644 --- a/crates/analytics/src/payments/distribution.rs +++ b/crates/analytics/src/payments/distribution.rs @@ -2,7 +2,7 @@ use api_models::analytics::{ payments::{ PaymentDimensions, PaymentDistributions, PaymentFilters, PaymentMetricsBucketIdentifier, }, - Distribution, Granularity, TimeRange, + Granularity, PaymentDistributionBody, TimeRange, }; use diesel_models::enums as storage_enums; use time::PrimitiveDateTime; @@ -28,6 +28,12 @@ pub struct PaymentDistributionRow { pub client_source: Option, pub client_version: Option, pub profile_id: Option, + pub card_network: Option, + pub merchant_id: Option, + pub card_last_4: Option, + pub card_issuer: Option, + pub error_reason: Option, + pub first_attempt: Option, pub total: Option, pub count: Option, pub error_message: Option, @@ -47,11 +53,11 @@ where #[allow(clippy::too_many_arguments)] async fn load_distribution( &self, - distribution: &Distribution, + distribution: &PaymentDistributionBody, dimensions: &[PaymentDimensions], auth: &AuthInfo, filters: &PaymentFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult>; @@ -69,11 +75,11 @@ where { async fn load_distribution( &self, - distribution: &Distribution, + distribution: &PaymentDistributionBody, dimensions: &[PaymentDimensions], auth: &AuthInfo, filters: &PaymentFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { diff --git a/crates/analytics/src/payments/distribution/payment_error_message.rs b/crates/analytics/src/payments/distribution/payment_error_message.rs index 087ec5a640e8..0a92cfbe4700 100644 --- a/crates/analytics/src/payments/distribution/payment_error_message.rs +++ b/crates/analytics/src/payments/distribution/payment_error_message.rs @@ -1,6 +1,6 @@ use api_models::analytics::{ payments::{PaymentDimensions, PaymentFilters, PaymentMetricsBucketIdentifier}, - Distribution, Granularity, TimeRange, + Granularity, PaymentDistributionBody, TimeRange, }; use common_utils::errors::ReportSwitchExt; use diesel_models::enums as storage_enums; @@ -31,11 +31,11 @@ where { async fn load_distribution( &self, - distribution: &Distribution, + distribution: &PaymentDistributionBody, dimensions: &[PaymentDimensions], auth: &AuthInfo, filters: &PaymentFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -89,7 +89,7 @@ where .attach_printable("Error grouping by distribution_for") .switch()?; - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { granularity .set_group_by_clause(&mut query_builder) .attach_printable("Error adding granularity") @@ -155,6 +155,11 @@ where i.client_source.clone(), i.client_version.clone(), i.profile_id.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.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 094fd574169b..668bdaa6c8bc 100644 --- a/crates/analytics/src/payments/filters.rs +++ b/crates/analytics/src/payments/filters.rs @@ -60,4 +60,9 @@ pub struct PaymentFilterRow { pub client_version: Option, pub profile_id: Option, pub card_network: Option, + pub merchant_id: Option, + pub card_last_4: Option, + pub card_issuer: Option, + pub error_reason: Option, + pub first_attempt: Option, } diff --git a/crates/analytics/src/payments/metrics.rs b/crates/analytics/src/payments/metrics.rs index 731f03a247a2..71d2e57d7fa3 100644 --- a/crates/analytics/src/payments/metrics.rs +++ b/crates/analytics/src/payments/metrics.rs @@ -19,6 +19,7 @@ mod payment_count; mod payment_processed_amount; mod payment_success_count; mod retries_count; +mod sessionized_metrics; mod success_rate; use avg_ticket_size::AvgTicketSize; @@ -41,6 +42,12 @@ pub struct PaymentMetricRow { pub client_source: Option, pub client_version: Option, pub profile_id: Option, + pub card_network: Option, + pub merchant_id: Option, + pub card_last_4: Option, + pub card_issuer: Option, + pub error_reason: Option, + pub first_attempt: Option, pub total: Option, pub count: Option, #[serde(with = "common_utils::custom_serde::iso8601::option")] @@ -61,7 +68,7 @@ where dimensions: &[PaymentDimensions], auth: &AuthInfo, filters: &PaymentFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult>; @@ -82,7 +89,7 @@ where dimensions: &[PaymentDimensions], auth: &AuthInfo, filters: &PaymentFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -122,6 +129,51 @@ where .load_metrics(dimensions, auth, filters, granularity, time_range, pool) .await } + Self::SessionizedPaymentSuccessRate => { + sessionized_metrics::PaymentSuccessRate + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } + Self::SessionizedPaymentCount => { + sessionized_metrics::PaymentCount + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } + Self::SessionizedPaymentSuccessCount => { + sessionized_metrics::PaymentSuccessCount + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } + Self::SessionizedPaymentProcessedAmount => { + sessionized_metrics::PaymentProcessedAmount + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } + Self::SessionizedAvgTicketSize => { + sessionized_metrics::AvgTicketSize + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } + Self::SessionizedRetriesCount => { + sessionized_metrics::RetriesCount + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } + Self::SessionizedConnectorSuccessRate => { + sessionized_metrics::ConnectorSuccessRate + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } + Self::PaymentsDistribution => { + sessionized_metrics::PaymentsDistribution + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } + Self::FailureReasons => { + sessionized_metrics::FailureReasons + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } } } } diff --git a/crates/analytics/src/payments/metrics/avg_ticket_size.rs b/crates/analytics/src/payments/metrics/avg_ticket_size.rs index ec76bfc86dcc..10f350e30ce4 100644 --- a/crates/analytics/src/payments/metrics/avg_ticket_size.rs +++ b/crates/analytics/src/payments/metrics/avg_ticket_size.rs @@ -34,7 +34,7 @@ where dimensions: &[PaymentDimensions], auth: &AuthInfo, filters: &PaymentFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -85,7 +85,7 @@ where .switch()?; } - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { granularity .set_group_by_clause(&mut query_builder) .attach_printable("Error adding granularity") @@ -117,6 +117,11 @@ where i.client_source.clone(), i.client_version.clone(), i.profile_id.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.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 e44e87a7e5f2..9e6bf31fbfb9 100644 --- a/crates/analytics/src/payments/metrics/connector_success_rate.rs +++ b/crates/analytics/src/payments/metrics/connector_success_rate.rs @@ -36,7 +36,7 @@ where dimensions: &[PaymentDimensions], auth: &AuthInfo, filters: &PaymentFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -87,7 +87,7 @@ where .switch()?; } - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { granularity .set_group_by_clause(&mut query_builder) .attach_printable("Error adding granularity") @@ -112,6 +112,11 @@ where i.client_source.clone(), i.client_version.clone(), i.profile_id.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.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 642ba9ef0359..297aef4fec50 100644 --- a/crates/analytics/src/payments/metrics/payment_count.rs +++ b/crates/analytics/src/payments/metrics/payment_count.rs @@ -33,7 +33,7 @@ where dimensions: &[PaymentDimensions], auth: &AuthInfo, filters: &PaymentFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -78,7 +78,7 @@ where .switch()?; } - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { granularity .set_group_by_clause(&mut query_builder) .attach_printable("Error adding granularity") @@ -103,6 +103,11 @@ where i.client_source.clone(), i.client_version.clone(), i.profile_id.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.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 c31b8873cb8c..958469246650 100644 --- a/crates/analytics/src/payments/metrics/payment_processed_amount.rs +++ b/crates/analytics/src/payments/metrics/payment_processed_amount.rs @@ -34,7 +34,7 @@ where dimensions: &[PaymentDimensions], auth: &AuthInfo, filters: &PaymentFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -50,6 +50,7 @@ where alias: Some("total"), }) .switch()?; + query_builder.add_select_column("currency").switch()?; query_builder .add_select_column(Aggregate::Min { field: "created_at", @@ -79,7 +80,12 @@ where .switch()?; } - if let Some(granularity) = granularity.as_ref() { + query_builder + .add_group_by_clause("currency") + .attach_printable("Error grouping by currency") + .switch()?; + + if let Some(granularity) = granularity { granularity .set_group_by_clause(&mut query_builder) .attach_printable("Error adding granularity") @@ -111,6 +117,11 @@ where i.client_source.clone(), i.client_version.clone(), i.profile_id.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.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 a45a4557c523..843e28589772 100644 --- a/crates/analytics/src/payments/metrics/payment_success_count.rs +++ b/crates/analytics/src/payments/metrics/payment_success_count.rs @@ -34,7 +34,7 @@ where dimensions: &[PaymentDimensions], auth: &AuthInfo, filters: &PaymentFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -79,7 +79,7 @@ where .switch()?; } - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { granularity .set_group_by_clause(&mut query_builder) .attach_printable("Error adding granularity") @@ -110,6 +110,11 @@ where i.client_source.clone(), i.client_version.clone(), i.profile_id.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.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 c3c7d40e3214..ced845651cf3 100644 --- a/crates/analytics/src/payments/metrics/retries_count.rs +++ b/crates/analytics/src/payments/metrics/retries_count.rs @@ -39,7 +39,7 @@ where _dimensions: &[PaymentDimensions], auth: &AuthInfo, _filters: &PaymentFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -82,7 +82,7 @@ where .attach_printable("Error filtering time range") .switch()?; - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { granularity .set_group_by_clause(&mut query_builder) .attach_printable("Error adding granularity") @@ -107,6 +107,11 @@ where i.client_source.clone(), i.client_version.clone(), i.profile_id.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.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/sessionized_metrics.rs b/crates/analytics/src/payments/metrics/sessionized_metrics.rs new file mode 100644 index 000000000000..e3a5e370534c --- /dev/null +++ b/crates/analytics/src/payments/metrics/sessionized_metrics.rs @@ -0,0 +1,20 @@ +mod avg_ticket_size; +mod connector_success_rate; +mod failure_reasons; +mod payment_count; +mod payment_processed_amount; +mod payment_success_count; +mod payments_distribution; +mod retries_count; +mod success_rate; +pub(super) use avg_ticket_size::AvgTicketSize; +pub(super) use connector_success_rate::ConnectorSuccessRate; +pub(super) use failure_reasons::FailureReasons; +pub(super) use payment_count::PaymentCount; +pub(super) use payment_processed_amount::PaymentProcessedAmount; +pub(super) use payment_success_count::PaymentSuccessCount; +pub(super) use payments_distribution::PaymentsDistribution; +pub(super) use retries_count::RetriesCount; +pub(super) use success_rate::PaymentSuccessRate; + +pub use super::{PaymentMetric, PaymentMetricAnalytics, PaymentMetricRow}; diff --git a/crates/analytics/src/payments/metrics/sessionized_metrics/avg_ticket_size.rs b/crates/analytics/src/payments/metrics/sessionized_metrics/avg_ticket_size.rs new file mode 100644 index 000000000000..e29c19bda882 --- /dev/null +++ b/crates/analytics/src/payments/metrics/sessionized_metrics/avg_ticket_size.rs @@ -0,0 +1,146 @@ +use std::collections::HashSet; + +use api_models::analytics::{ + payments::{PaymentDimensions, PaymentFilters, PaymentMetricsBucketIdentifier}, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use diesel_models::enums as storage_enums; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::{PaymentMetric, PaymentMetricRow}; +use crate::{ + enums::AuthInfo, + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(crate) struct AvgTicketSize; + +#[async_trait::async_trait] +impl PaymentMetric for AvgTicketSize +where + T: AnalyticsDataSource + super::PaymentMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[PaymentDimensions], + auth: &AuthInfo, + filters: &PaymentFilters, + granularity: Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> { + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::PaymentSessionized); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + + query_builder + .add_select_column(Aggregate::Sum { + field: "amount", + alias: Some("total"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + + if let Some(granularity) = granularity { + granularity + .set_group_by_clause(&mut query_builder) + .attach_printable("Error adding granularity") + .switch()?; + } + + query_builder + .add_filter_clause( + PaymentDimensions::PaymentStatus, + storage_enums::AttemptStatus::Charged, + ) + .switch()?; + + query_builder + .execute_query::(pool) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(|i| { + Ok(( + PaymentMetricsBucketIdentifier::new( + i.currency.as_ref().map(|i| i.0), + i.status.as_ref().map(|i| i.0), + i.connector.clone(), + 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(), + i.profile_id.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/payments/metrics/sessionized_metrics/connector_success_rate.rs b/crates/analytics/src/payments/metrics/sessionized_metrics/connector_success_rate.rs new file mode 100644 index 000000000000..d8ee7fcb473b --- /dev/null +++ b/crates/analytics/src/payments/metrics/sessionized_metrics/connector_success_rate.rs @@ -0,0 +1,141 @@ +use std::collections::HashSet; + +use api_models::analytics::{ + payments::{PaymentDimensions, PaymentFilters, PaymentMetricsBucketIdentifier}, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::PaymentMetricRow; +use crate::{ + enums::AuthInfo, + query::{ + Aggregate, FilterTypes, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, + Window, + }, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(crate) struct ConnectorSuccessRate; + +#[async_trait::async_trait] +impl super::PaymentMetric for ConnectorSuccessRate +where + T: AnalyticsDataSource + super::PaymentMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[PaymentDimensions], + auth: &AuthInfo, + filters: &PaymentFilters, + granularity: Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> { + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::PaymentSessionized); + let mut dimensions = dimensions.to_vec(); + + dimensions.push(PaymentDimensions::PaymentStatus); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + query_builder + .add_custom_filter_clause(PaymentDimensions::Connector, "NULL", FilterTypes::IsNotNull) + .switch()?; + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + + if let Some(granularity) = granularity { + granularity + .set_group_by_clause(&mut query_builder) + .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(( + PaymentMetricsBucketIdentifier::new( + i.currency.as_ref().map(|i| i.0), + None, + i.connector.clone(), + 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(), + i.profile_id.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/payments/metrics/sessionized_metrics/failure_reasons.rs b/crates/analytics/src/payments/metrics/sessionized_metrics/failure_reasons.rs new file mode 100644 index 000000000000..d6944d8d0bdf --- /dev/null +++ b/crates/analytics/src/payments/metrics/sessionized_metrics/failure_reasons.rs @@ -0,0 +1,206 @@ +use std::collections::HashSet; + +use api_models::analytics::{ + payments::{PaymentDimensions, PaymentFilters, PaymentMetricsBucketIdentifier}, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use diesel_models::enums as storage_enums; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::PaymentMetricRow; +use crate::{ + enums::AuthInfo, + query::{ + Aggregate, FilterTypes, GroupByClause, Order, QueryBuilder, QueryFilter, SeriesBucket, + ToSql, Window, + }, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(crate) struct FailureReasons; + +#[async_trait::async_trait] +impl super::PaymentMetric for FailureReasons +where + T: AnalyticsDataSource + super::PaymentMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[PaymentDimensions], + auth: &AuthInfo, + filters: &PaymentFilters, + granularity: Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> { + let mut inner_query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::PaymentSessionized); + inner_query_builder + .add_select_column("sum(sign_flag)") + .switch()?; + + inner_query_builder + .add_custom_filter_clause( + PaymentDimensions::ErrorReason, + "NULL", + FilterTypes::IsNotNull, + ) + .switch()?; + + time_range + .set_filter_clause(&mut inner_query_builder) + .attach_printable("Error filtering time range for inner query") + .switch()?; + + let inner_query_string = inner_query_builder + .build_query() + .attach_printable("Error building inner query") + .change_context(MetricsError::QueryBuildingError)?; + + let mut outer_query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::PaymentSessionized); + + for dim in dimensions.iter() { + outer_query_builder.add_select_column(dim).switch()?; + } + + outer_query_builder + .add_select_column("sum(sign_flag) AS count") + .switch()?; + + outer_query_builder + .add_select_column(format!("({}) AS total", inner_query_string)) + .switch()?; + + outer_query_builder + .add_select_column("first_attempt") + .switch()?; + + outer_query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + + outer_query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters + .set_filter_clause(&mut outer_query_builder) + .switch()?; + + auth.set_filter_clause(&mut outer_query_builder).switch()?; + + time_range + .set_filter_clause(&mut outer_query_builder) + .attach_printable("Error filtering time range for outer query") + .switch()?; + + outer_query_builder + .add_filter_clause( + PaymentDimensions::PaymentStatus, + storage_enums::AttemptStatus::Failure, + ) + .switch()?; + + outer_query_builder + .add_custom_filter_clause( + PaymentDimensions::ErrorReason, + "NULL", + FilterTypes::IsNotNull, + ) + .switch()?; + + for dim in dimensions.iter() { + outer_query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + + outer_query_builder + .add_group_by_clause("first_attempt") + .attach_printable("Error grouping by first_attempt") + .switch()?; + + if let Some(granularity) = granularity { + granularity + .set_group_by_clause(&mut outer_query_builder) + .attach_printable("Error adding granularity") + .switch()?; + } + + outer_query_builder + .add_order_by_clause("count", Order::Descending) + .attach_printable("Error adding order by clause") + .switch()?; + + let filtered_dimensions: Vec<&PaymentDimensions> = dimensions + .iter() + .filter(|&&dim| dim != PaymentDimensions::ErrorReason) + .collect(); + + for dim in &filtered_dimensions { + outer_query_builder + .add_order_by_clause(*dim, Order::Ascending) + .attach_printable("Error adding order by clause") + .switch()?; + } + + outer_query_builder + .execute_query::(pool) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(|i| { + Ok(( + PaymentMetricsBucketIdentifier::new( + i.currency.as_ref().map(|i| i.0), + None, + i.connector.clone(), + 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(), + i.profile_id.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/payments/metrics/sessionized_metrics/payment_count.rs b/crates/analytics/src/payments/metrics/sessionized_metrics/payment_count.rs new file mode 100644 index 000000000000..98735b9383a6 --- /dev/null +++ b/crates/analytics/src/payments/metrics/sessionized_metrics/payment_count.rs @@ -0,0 +1,129 @@ +use std::collections::HashSet; + +use api_models::analytics::{ + payments::{PaymentDimensions, PaymentFilters, PaymentMetricsBucketIdentifier}, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::PaymentMetricRow; +use crate::{ + enums::AuthInfo, + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(crate) struct PaymentCount; + +#[async_trait::async_trait] +impl super::PaymentMetric for PaymentCount +where + T: AnalyticsDataSource + super::PaymentMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[PaymentDimensions], + auth: &AuthInfo, + filters: &PaymentFilters, + granularity: Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> { + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::PaymentSessionized); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + + if let Some(granularity) = granularity { + granularity + .set_group_by_clause(&mut query_builder) + .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(( + PaymentMetricsBucketIdentifier::new( + i.currency.as_ref().map(|i| i.0), + i.status.as_ref().map(|i| i.0), + i.connector.clone(), + 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(), + i.profile_id.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, crate::query::PostProcessingError>>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/payments/metrics/sessionized_metrics/payment_processed_amount.rs b/crates/analytics/src/payments/metrics/sessionized_metrics/payment_processed_amount.rs new file mode 100644 index 000000000000..453f988d229d --- /dev/null +++ b/crates/analytics/src/payments/metrics/sessionized_metrics/payment_processed_amount.rs @@ -0,0 +1,163 @@ +use std::collections::HashSet; + +use api_models::analytics::{ + payments::{PaymentDimensions, PaymentFilters, PaymentMetricsBucketIdentifier}, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use diesel_models::enums as storage_enums; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::PaymentMetricRow; +use crate::{ + enums::AuthInfo, + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(crate) struct PaymentProcessedAmount; + +#[async_trait::async_trait] +impl super::PaymentMetric for PaymentProcessedAmount +where + T: AnalyticsDataSource + super::PaymentMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[PaymentDimensions], + auth: &AuthInfo, + filters: &PaymentFilters, + granularity: Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> { + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::PaymentSessionized); + + let mut dimensions = dimensions.to_vec(); + + dimensions.push(PaymentDimensions::PaymentStatus); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + + query_builder.add_select_column("first_attempt").switch()?; + + query_builder.add_select_column("currency").switch()?; + + query_builder + .add_select_column(Aggregate::Sum { + field: "amount", + alias: Some("total"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + query_builder + .add_group_by_clause("first_attempt") + .attach_printable("Error grouping by first_attempt") + .switch()?; + + query_builder + .add_group_by_clause("currency") + .attach_printable("Error grouping by currency") + .switch()?; + + if let Some(granularity) = granularity { + granularity + .set_group_by_clause(&mut query_builder) + .attach_printable("Error adding granularity") + .switch()?; + } + + query_builder + .add_filter_clause( + PaymentDimensions::PaymentStatus, + storage_enums::AttemptStatus::Charged, + ) + .switch()?; + + query_builder + .execute_query::(pool) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(|i| { + Ok(( + PaymentMetricsBucketIdentifier::new( + i.currency.as_ref().map(|i| i.0), + None, + i.connector.clone(), + 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(), + i.profile_id.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/payments/metrics/sessionized_metrics/payment_success_count.rs b/crates/analytics/src/payments/metrics/sessionized_metrics/payment_success_count.rs new file mode 100644 index 000000000000..14391588b484 --- /dev/null +++ b/crates/analytics/src/payments/metrics/sessionized_metrics/payment_success_count.rs @@ -0,0 +1,139 @@ +use std::collections::HashSet; + +use api_models::analytics::{ + payments::{PaymentDimensions, PaymentFilters, PaymentMetricsBucketIdentifier}, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use diesel_models::enums as storage_enums; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::PaymentMetricRow; +use crate::{ + enums::AuthInfo, + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(crate) struct PaymentSuccessCount; + +#[async_trait::async_trait] +impl super::PaymentMetric for PaymentSuccessCount +where + T: AnalyticsDataSource + super::PaymentMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[PaymentDimensions], + auth: &AuthInfo, + filters: &PaymentFilters, + granularity: Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> { + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::PaymentSessionized); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + + if let Some(granularity) = granularity { + granularity + .set_group_by_clause(&mut query_builder) + .attach_printable("Error adding granularity") + .switch()?; + } + + query_builder + .add_filter_clause( + PaymentDimensions::PaymentStatus, + storage_enums::AttemptStatus::Charged, + ) + .switch()?; + query_builder + .execute_query::(pool) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(|i| { + Ok(( + PaymentMetricsBucketIdentifier::new( + i.currency.as_ref().map(|i| i.0), + None, + i.connector.clone(), + 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(), + i.profile_id.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/payments/metrics/sessionized_metrics/payments_distribution.rs b/crates/analytics/src/payments/metrics/sessionized_metrics/payments_distribution.rs new file mode 100644 index 000000000000..5c6a8c6adecf --- /dev/null +++ b/crates/analytics/src/payments/metrics/sessionized_metrics/payments_distribution.rs @@ -0,0 +1,142 @@ +use std::collections::HashSet; + +use api_models::analytics::{ + payments::{PaymentDimensions, PaymentFilters, PaymentMetricsBucketIdentifier}, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::PaymentMetricRow; +use crate::{ + enums::AuthInfo, + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(crate) struct PaymentsDistribution; + +#[async_trait::async_trait] +impl super::PaymentMetric for PaymentsDistribution +where + T: AnalyticsDataSource + super::PaymentMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[PaymentDimensions], + auth: &AuthInfo, + filters: &PaymentFilters, + granularity: Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> { + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::PaymentSessionized); + + let mut dimensions = dimensions.to_vec(); + + dimensions.push(PaymentDimensions::PaymentStatus); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + + query_builder.add_select_column("first_attempt").switch()?; + + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + query_builder + .add_group_by_clause("first_attempt") + .attach_printable("Error grouping by first_attempt") + .switch()?; + if let Some(granularity) = granularity { + granularity + .set_group_by_clause(&mut query_builder) + .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(( + PaymentMetricsBucketIdentifier::new( + i.currency.as_ref().map(|i| i.0), + None, + i.connector.clone(), + 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(), + i.profile_id.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/payments/metrics/sessionized_metrics/retries_count.rs b/crates/analytics/src/payments/metrics/sessionized_metrics/retries_count.rs new file mode 100644 index 000000000000..7d81c48274b1 --- /dev/null +++ b/crates/analytics/src/payments/metrics/sessionized_metrics/retries_count.rs @@ -0,0 +1,135 @@ +use std::collections::HashSet; + +use api_models::{ + analytics::{ + payments::{PaymentDimensions, PaymentFilters, PaymentMetricsBucketIdentifier}, + Granularity, TimeRange, + }, + enums::IntentStatus, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::PaymentMetricRow; +use crate::{ + enums::AuthInfo, + query::{ + Aggregate, FilterTypes, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, + Window, + }, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(crate) struct RetriesCount; + +#[async_trait::async_trait] +impl super::PaymentMetric for RetriesCount +where + T: AnalyticsDataSource + super::PaymentMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + _dimensions: &[PaymentDimensions], + auth: &AuthInfo, + _filters: &PaymentFilters, + granularity: Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> { + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::PaymentIntentSessionized); + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Sum { + field: "amount", + alias: Some("total"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + auth.set_filter_clause(&mut query_builder).switch()?; + + query_builder + .add_custom_filter_clause("attempt_count", "1", FilterTypes::Gt) + .switch()?; + query_builder + .add_custom_filter_clause("status", IntentStatus::Succeeded, FilterTypes::Equal) + .switch()?; + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + if let Some(granularity) = granularity { + granularity + .set_group_by_clause(&mut query_builder) + .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(( + PaymentMetricsBucketIdentifier::new( + i.currency.as_ref().map(|i| i.0), + None, + i.connector.clone(), + 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(), + i.profile_id.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/payments/metrics/sessionized_metrics/success_rate.rs b/crates/analytics/src/payments/metrics/sessionized_metrics/success_rate.rs new file mode 100644 index 000000000000..f20308ed3b1b --- /dev/null +++ b/crates/analytics/src/payments/metrics/sessionized_metrics/success_rate.rs @@ -0,0 +1,135 @@ +use std::collections::HashSet; + +use api_models::analytics::{ + payments::{PaymentDimensions, PaymentFilters, PaymentMetricsBucketIdentifier}, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::PaymentMetricRow; +use crate::{ + enums::AuthInfo, + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(crate) struct PaymentSuccessRate; + +#[async_trait::async_trait] +impl super::PaymentMetric for PaymentSuccessRate +where + T: AnalyticsDataSource + super::PaymentMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[PaymentDimensions], + auth: &AuthInfo, + filters: &PaymentFilters, + granularity: Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> { + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::PaymentSessionized); + let mut dimensions = dimensions.to_vec(); + + dimensions.push(PaymentDimensions::PaymentStatus); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + + if let Some(granularity) = granularity { + granularity + .set_group_by_clause(&mut query_builder) + .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(( + PaymentMetricsBucketIdentifier::new( + i.currency.as_ref().map(|i| i.0), + None, + i.connector.clone(), + 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(), + i.profile_id.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/payments/metrics/success_rate.rs b/crates/analytics/src/payments/metrics/success_rate.rs index b8e840c2203b..6698fe8ce654 100644 --- a/crates/analytics/src/payments/metrics/success_rate.rs +++ b/crates/analytics/src/payments/metrics/success_rate.rs @@ -33,7 +33,7 @@ where dimensions: &[PaymentDimensions], auth: &AuthInfo, filters: &PaymentFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -81,7 +81,7 @@ where .switch()?; } - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { granularity .set_group_by_clause(&mut query_builder) .attach_printable("Error adding granularity") @@ -106,6 +106,11 @@ where i.client_source.clone(), i.client_version.clone(), i.profile_id.clone(), + i.card_network.clone(), + i.merchant_id.clone(), + i.card_last_4.clone(), + i.card_issuer.clone(), + i.error_reason.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 b7984a19ea3f..b9af3cd06105 100644 --- a/crates/analytics/src/payments/types.rs +++ b/crates/analytics/src/payments/types.rs @@ -48,7 +48,7 @@ where PaymentDimensions::PaymentMethodType, &self.payment_method_type, ) - .attach_printable("Error adding payment method filter")?; + .attach_printable("Error adding payment method type filter")?; } if !self.client_source.is_empty() { builder @@ -84,6 +84,31 @@ where ) .attach_printable("Error adding card network filter")?; } + if !self.merchant_id.is_empty() { + builder + .add_filter_in_range_clause(PaymentDimensions::MerchantId, &self.merchant_id) + .attach_printable("Error adding merchant id filter")?; + } + if !self.card_last_4.is_empty() { + builder + .add_filter_in_range_clause(PaymentDimensions::CardLast4, &self.card_last_4) + .attach_printable("Error adding card last 4 filter")?; + } + if !self.card_issuer.is_empty() { + builder + .add_filter_in_range_clause(PaymentDimensions::CardIssuer, &self.card_issuer) + .attach_printable("Error adding card issuer filter")?; + } + if !self.error_reason.is_empty() { + builder + .add_filter_in_range_clause(PaymentDimensions::ErrorReason, &self.error_reason) + .attach_printable("Error adding error reason filter")?; + } + if !self.first_attempt.is_empty() { + builder + .add_filter_in_range_clause("first_attempt", &self.first_attempt) + .attach_printable("Error adding first attempt filter")?; + } Ok(()) } } diff --git a/crates/analytics/src/query.rs b/crates/analytics/src/query.rs index 5f8a2f4a3304..f449ba2f9b52 100644 --- a/crates/analytics/src/query.rs +++ b/crates/analytics/src/query.rs @@ -1,4 +1,4 @@ -use std::marker::PhantomData; +use std::{fmt, marker::PhantomData}; use api_models::{ analytics::{ @@ -9,7 +9,7 @@ use api_models::{ frm::{FrmDimensions, FrmTransactionType}, payment_intents::PaymentIntentDimensions, payments::{PaymentDimensions, PaymentDistributions}, - refunds::{RefundDimensions, RefundType}, + refunds::{RefundDimensions, RefundDistributions, RefundType}, sdk_events::{SdkEventDimensions, SdkEventNames}, Granularity, }, @@ -301,8 +301,8 @@ pub enum Order { Descending, } -impl std::fmt::Display for Order { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for Order { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Ascending => write!(f, "asc"), Self::Descending => write!(f, "desc"), @@ -335,6 +335,68 @@ pub struct TopN { pub order: Order, } +#[derive(Debug, Clone)] +pub struct LimitByClause { + limit: u64, + columns: Vec, +} + +impl fmt::Display for LimitByClause { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "LIMIT {} BY {}", self.limit, self.columns.join(", ")) + } +} + +#[derive(Debug, Default, Clone, Copy)] +pub enum FilterCombinator { + #[default] + And, + Or, +} + +impl ToSql for FilterCombinator { + fn to_sql(&self, _table_engine: &TableEngine) -> error_stack::Result { + Ok(match self { + Self::And => " AND ", + Self::Or => " OR ", + } + .to_owned()) + } +} + +#[derive(Debug, Clone)] +pub enum Filter { + Plain(String, FilterTypes, String), + NestedFilter(FilterCombinator, Vec), +} + +impl Default for Filter { + fn default() -> Self { + Self::NestedFilter(FilterCombinator::default(), Vec::new()) + } +} + +impl ToSql for Filter { + fn to_sql(&self, table_engine: &TableEngine) -> error_stack::Result { + Ok(match self { + Self::Plain(l, op, r) => filter_type_to_sql(l, *op, r), + Self::NestedFilter(operator, filters) => { + format!( + "( {} )", + filters + .iter() + .map(|f| >::to_sql(f, table_engine)) + .collect::, _>>()? + .join( + >::to_sql(operator, table_engine)? + .as_ref() + ) + ) + } + }) + } +} + #[derive(Debug)] pub struct QueryBuilder where @@ -342,9 +404,11 @@ where AnalyticsCollection: ToSql, { columns: Vec, - filters: Vec<(String, FilterTypes, String)>, + filters: Filter, group_by: Vec, + order_by: Vec, having: Option>, + limit_by: Option, outer_select: Vec, top_n: Option, table: AnalyticsCollection, @@ -387,6 +451,19 @@ impl ToSql for &common_utils::id_type::PaymentId { } } +impl ToSql for common_utils::id_type::CustomerId { + fn to_sql(&self, _table_engine: &TableEngine) -> error_stack::Result { + Ok(self.get_string_repr().to_owned()) + } +} + +impl ToSql for bool { + fn to_sql(&self, _table_engine: &TableEngine) -> error_stack::Result { + let flag = *self; + Ok(i8::from(flag).to_string()) + } +} + /// Implement `ToSql` on arrays of types that impl `ToString`. macro_rules! impl_to_sql_for_to_string { ($($type:ty),+) => { @@ -411,6 +488,7 @@ impl_to_sql_for_to_string!( PaymentIntentDimensions, &PaymentDistributions, RefundDimensions, + &RefundDistributions, FrmDimensions, PaymentMethod, PaymentMethodType, @@ -444,7 +522,7 @@ impl_to_sql_for_to_string!( DisputeStage ); -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub enum FilterTypes { Equal, NotEqual, @@ -458,7 +536,7 @@ pub enum FilterTypes { IsNotNull, } -pub fn filter_type_to_sql(l: &String, op: &FilterTypes, r: &String) -> String { +pub fn filter_type_to_sql(l: &str, op: FilterTypes, r: &str) -> String { match op { FilterTypes::EqualBool => format!("{l} = {r}"), FilterTypes::Equal => format!("{l} = '{r}'"), @@ -483,7 +561,9 @@ where columns: Default::default(), filters: Default::default(), group_by: Default::default(), + order_by: Default::default(), having: Default::default(), + limit_by: Default::default(), outer_select: Default::default(), top_n: Default::default(), table, @@ -580,7 +660,7 @@ where rhs: impl ToSql, comparison: FilterTypes, ) -> QueryResult<()> { - self.filters.push(( + let filter = Filter::Plain( lhs.to_sql(&self.table_engine) .change_context(QueryBuildingError::SqlSerializeError) .attach_printable("Error serializing filter key")?, @@ -588,9 +668,18 @@ where rhs.to_sql(&self.table_engine) .change_context(QueryBuildingError::SqlSerializeError) .attach_printable("Error serializing filter value")?, - )); + ); + self.add_nested_filter_clause(filter); Ok(()) } + pub fn add_nested_filter_clause(&mut self, filter: Filter) { + match &mut self.filters { + Filter::NestedFilter(_, ref mut filters) => filters.push(filter), + f @ Filter::Plain(_, _, _) => { + self.filters = Filter::NestedFilter(FilterCombinator::And, vec![f.clone(), filter]); + } + } + } pub fn add_filter_in_range_clause( &mut self, @@ -623,7 +712,38 @@ where Ok(()) } - pub fn add_granularity_in_mins(&mut self, granularity: &Granularity) -> QueryResult<()> { + pub fn add_order_by_clause( + &mut self, + column: impl ToSql, + order: impl ToSql, + ) -> QueryResult<()> { + let column_sql = column + .to_sql(&self.table_engine) + .change_context(QueryBuildingError::SqlSerializeError) + .attach_printable("Error serializing order by column")?; + + let order_sql = order + .to_sql(&self.table_engine) + .change_context(QueryBuildingError::SqlSerializeError) + .attach_printable("Error serializing order direction")?; + + self.order_by.push(format!("{} {}", column_sql, order_sql)); + Ok(()) + } + + pub fn set_limit_by(&mut self, limit: u64, columns: &[impl ToSql]) -> QueryResult<()> { + let columns = columns + .iter() + .map(|col| col.to_sql(&self.table_engine)) + .collect::, _>>() + .change_context(QueryBuildingError::SqlSerializeError) + .attach_printable("Error serializing LIMIT BY columns")?; + + self.limit_by = Some(LimitByClause { limit, columns }); + Ok(()) + } + + pub fn add_granularity_in_mins(&mut self, granularity: Granularity) -> QueryResult<()> { let interval = match granularity { Granularity::OneMin => "1", Granularity::FiveMin => "5", @@ -638,12 +758,9 @@ where Ok(()) } - fn get_filter_clause(&self) -> String { - self.filters - .iter() - .map(|(l, op, r)| filter_type_to_sql(l, op, r)) - .collect::>() - .join(" AND ") + fn get_filter_clause(&self) -> QueryResult { + >::to_sql(&self.filters, &self.table_engine) + .change_context(QueryBuildingError::SqlSerializeError) } fn get_select_clause(&self) -> String { @@ -697,7 +814,7 @@ where pub fn get_filter_type_clause(&self) -> Option { self.having.as_ref().map(|vec| { vec.iter() - .map(|(l, op, r)| filter_type_to_sql(l, op, r)) + .map(|(l, op, r)| filter_type_to_sql(l, *op, r)) .collect::>() .join(" AND ") }) @@ -731,9 +848,10 @@ where .attach_printable("Error serializing table value")?, ); - if !self.filters.is_empty() { + let filter_clause = self.get_filter_clause()?; + if !filter_clause.is_empty() { query.push_str(" WHERE "); - query.push_str(&self.get_filter_clause()); + query.push_str(filter_clause.as_str()); } if !self.group_by.is_empty() { @@ -758,6 +876,15 @@ where } } + if !self.order_by.is_empty() { + query.push_str(" ORDER BY "); + query.push_str(&self.order_by.join(", ")); + } + + if let Some(limit_by) = &self.limit_by { + query.push_str(&format!(" {}", limit_by)); + } + if !self.outer_select.is_empty() { query.insert_str( 0, diff --git a/crates/analytics/src/refunds.rs b/crates/analytics/src/refunds.rs index 590dc148ebfa..ed6f396cceca 100644 --- a/crates/analytics/src/refunds.rs +++ b/crates/analytics/src/refunds.rs @@ -1,6 +1,7 @@ pub mod accumulator; mod core; +pub mod distribution; pub mod filters; pub mod metrics; pub mod types; diff --git a/crates/analytics/src/refunds/accumulator.rs b/crates/analytics/src/refunds/accumulator.rs index 9c51defdcf91..840d46bbab76 100644 --- a/crates/analytics/src/refunds/accumulator.rs +++ b/crates/analytics/src/refunds/accumulator.rs @@ -1,19 +1,56 @@ -use api_models::analytics::refunds::RefundMetricsBucketValue; +use api_models::analytics::refunds::{ + ErrorMessagesResult, ReasonsResult, RefundMetricsBucketValue, +}; +use bigdecimal::ToPrimitive; use diesel_models::enums as storage_enums; -use super::metrics::RefundMetricRow; +use super::{distribution::RefundDistributionRow, metrics::RefundMetricRow}; #[derive(Debug, Default)] pub struct RefundMetricsAccumulator { pub refund_success_rate: SuccessRateAccumulator, pub refund_count: CountAccumulator, pub refund_success: CountAccumulator, - pub processed_amount: SumAccumulator, + pub processed_amount: RefundProcessedAmountAccumulator, + pub refund_reason: RefundReasonAccumulator, + pub refund_reason_distribution: RefundReasonDistributionAccumulator, + pub refund_error_message: RefundReasonAccumulator, + pub refund_error_message_distribution: RefundErrorMessageDistributionAccumulator, } #[derive(Debug, Default)] -pub struct SuccessRateAccumulator { - pub success: i64, +pub struct RefundReasonDistributionRow { + pub count: i64, + pub total: i64, + pub refund_reason: String, +} + +#[derive(Debug, Default)] +pub struct RefundReasonDistributionAccumulator { + pub refund_reason_vec: Vec, +} + +#[derive(Debug, Default)] +pub struct RefundErrorMessageDistributionRow { + pub count: i64, pub total: i64, + pub refund_error_message: String, +} + +#[derive(Debug, Default)] +pub struct RefundErrorMessageDistributionAccumulator { + pub refund_error_message_vec: Vec, +} + +#[derive(Debug, Default)] +#[repr(transparent)] +pub struct RefundReasonAccumulator { + pub count: u64, +} + +#[derive(Debug, Default)] +pub struct SuccessRateAccumulator { + pub success: u32, + pub total: u32, } #[derive(Debug, Default)] #[repr(transparent)] @@ -21,8 +58,8 @@ pub struct CountAccumulator { pub count: Option, } #[derive(Debug, Default)] -#[repr(transparent)] -pub struct SumAccumulator { +pub struct RefundProcessedAmountAccumulator { + pub count: Option, pub total: Option, } @@ -34,6 +71,93 @@ pub trait RefundMetricAccumulator { fn collect(self) -> Self::MetricOutput; } +pub trait RefundDistributionAccumulator { + type DistributionOutput; + + fn add_distribution_bucket(&mut self, distribution: &RefundDistributionRow); + + fn collect(self) -> Self::DistributionOutput; +} + +impl RefundDistributionAccumulator for RefundReasonDistributionAccumulator { + type DistributionOutput = Option>; + + fn add_distribution_bucket(&mut self, distribution: &RefundDistributionRow) { + self.refund_reason_vec.push(RefundReasonDistributionRow { + count: distribution.count.unwrap_or_default(), + total: distribution + .total + .clone() + .map(|i| i.to_i64().unwrap_or_default()) + .unwrap_or_default(), + refund_reason: distribution.refund_reason.clone().unwrap_or_default(), + }) + } + + fn collect(mut self) -> Self::DistributionOutput { + if self.refund_reason_vec.is_empty() { + None + } else { + self.refund_reason_vec.sort_by(|a, b| b.count.cmp(&a.count)); + let mut res: Vec = Vec::new(); + for val in self.refund_reason_vec.into_iter() { + let perc = f64::from(u32::try_from(val.count).ok()?) * 100.0 + / f64::from(u32::try_from(val.total).ok()?); + + res.push(ReasonsResult { + reason: val.refund_reason, + count: val.count, + percentage: (perc * 100.0).round() / 100.0, + }) + } + + Some(res) + } + } +} + +impl RefundDistributionAccumulator for RefundErrorMessageDistributionAccumulator { + type DistributionOutput = Option>; + + fn add_distribution_bucket(&mut self, distribution: &RefundDistributionRow) { + self.refund_error_message_vec + .push(RefundErrorMessageDistributionRow { + count: distribution.count.unwrap_or_default(), + total: distribution + .total + .clone() + .map(|i| i.to_i64().unwrap_or_default()) + .unwrap_or_default(), + refund_error_message: distribution + .refund_error_message + .clone() + .unwrap_or_default(), + }) + } + + fn collect(mut self) -> Self::DistributionOutput { + if self.refund_error_message_vec.is_empty() { + None + } else { + self.refund_error_message_vec + .sort_by(|a, b| b.count.cmp(&a.count)); + let mut res: Vec = Vec::new(); + for val in self.refund_error_message_vec.into_iter() { + let perc = f64::from(u32::try_from(val.count).ok()?) * 100.0 + / f64::from(u32::try_from(val.total).ok()?); + + res.push(ErrorMessagesResult { + error_message: val.refund_error_message, + count: val.count, + percentage: (perc * 100.0).round() / 100.0, + }) + } + + Some(res) + } + } +} + impl RefundMetricAccumulator for CountAccumulator { type MetricOutput = Option; #[inline] @@ -50,59 +174,103 @@ impl RefundMetricAccumulator for CountAccumulator { } } -impl RefundMetricAccumulator for SumAccumulator { - type MetricOutput = Option; +impl RefundMetricAccumulator for RefundProcessedAmountAccumulator { + type MetricOutput = (Option, Option, Option); #[inline] fn add_metrics_bucket(&mut self, metrics: &RefundMetricRow) { self.total = match ( self.total, - metrics - .total - .as_ref() - .and_then(bigdecimal::ToPrimitive::to_i64), + metrics.total.as_ref().and_then(ToPrimitive::to_i64), ) { (None, None) => None, (None, i @ Some(_)) | (i @ Some(_), None) => i, (Some(a), Some(b)) => Some(a + b), - } + }; + + self.count = match (self.count, metrics.count) { + (None, None) => None, + (None, i @ Some(_)) | (i @ Some(_), None) => i, + (Some(a), Some(b)) => Some(a + b), + }; } #[inline] fn collect(self) -> Self::MetricOutput { - self.total.and_then(|i| u64::try_from(i).ok()) + let total = u64::try_from(self.total.unwrap_or_default()).ok(); + let count = self.count.and_then(|i| u64::try_from(i).ok()); + + (total, count, Some(0)) } } impl RefundMetricAccumulator for SuccessRateAccumulator { - type MetricOutput = Option; + type MetricOutput = (Option, Option, Option); fn add_metrics_bucket(&mut self, metrics: &RefundMetricRow) { if let Some(ref refund_status) = metrics.refund_status { if refund_status.as_ref() == &storage_enums::RefundStatus::Success { - self.success += metrics.count.unwrap_or_default(); + if let Some(success) = metrics + .count + .and_then(|success| u32::try_from(success).ok()) + { + self.success += success; + } } }; - self.total += metrics.count.unwrap_or_default(); + if let Some(total) = metrics.count.and_then(|total| u32::try_from(total).ok()) { + self.total += total; + } } fn collect(self) -> Self::MetricOutput { - if self.total <= 0 { - None + if self.total == 0 { + (None, None, None) } else { - Some( - f64::from(u32::try_from(self.success).ok()?) * 100.0 - / f64::from(u32::try_from(self.total).ok()?), - ) + let success = Some(self.success); + let total = Some(self.total); + let success_rate = match (success, total) { + (Some(s), Some(t)) if t > 0 => Some(f64::from(s) * 100.0 / f64::from(t)), + _ => None, + }; + (success, total, success_rate) + } + } +} + +impl RefundMetricAccumulator for RefundReasonAccumulator { + type MetricOutput = Option; + + fn add_metrics_bucket(&mut self, metrics: &RefundMetricRow) { + if let Some(count) = metrics.count { + if let Ok(count_u64) = u64::try_from(count) { + self.count += count_u64; + } } } + + fn collect(self) -> Self::MetricOutput { + Some(self.count) + } } impl RefundMetricsAccumulator { pub fn collect(self) -> RefundMetricsBucketValue { + let (successful_refunds, total_refunds, refund_success_rate) = + self.refund_success_rate.collect(); + let (refund_processed_amount, refund_processed_count, refund_processed_amount_in_usd) = + self.processed_amount.collect(); RefundMetricsBucketValue { - refund_success_rate: self.refund_success_rate.collect(), + successful_refunds, + total_refunds, + refund_success_rate, refund_count: self.refund_count.collect(), refund_success_count: self.refund_success.collect(), - refund_processed_amount: self.processed_amount.collect(), + refund_processed_amount, + refund_processed_amount_in_usd, + refund_processed_count, + refund_reason_distribution: self.refund_reason_distribution.collect(), + refund_error_message_distribution: self.refund_error_message_distribution.collect(), + refund_reason_count: self.refund_reason.collect(), + refund_error_message_count: self.refund_error_message.collect(), } } } diff --git a/crates/analytics/src/refunds/core.rs b/crates/analytics/src/refunds/core.rs index 9c4770c79eee..15ae14206ccf 100644 --- a/crates/analytics/src/refunds/core.rs +++ b/crates/analytics/src/refunds/core.rs @@ -1,37 +1,56 @@ #![allow(dead_code)] -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use api_models::analytics::{ refunds::{ - RefundDimensions, RefundMetrics, RefundMetricsBucketIdentifier, RefundMetricsBucketResponse, + RefundDimensions, RefundDistributions, RefundMetrics, RefundMetricsBucketIdentifier, + RefundMetricsBucketResponse, }, - AnalyticsMetadata, GetRefundFilterRequest, GetRefundMetricRequest, MetricsResponse, - RefundFilterValue, RefundFiltersResponse, + GetRefundFilterRequest, GetRefundMetricRequest, RefundFilterValue, RefundFiltersResponse, + RefundsAnalyticsMetadata, RefundsMetricsResponse, }; +use bigdecimal::ToPrimitive; +use common_enums::Currency; +use common_utils::errors::CustomResult; +use currency_conversion::{conversion::convert, types::ExchangeRates}; use error_stack::ResultExt; use router_env::{ logger, - metrics::add_attributes, tracing::{self, Instrument}, }; use super::{ + distribution::RefundDistributionRow, filters::{get_refund_filter_for_dimension, RefundFilterRow}, + metrics::RefundMetricRow, RefundMetricsAccumulator, }; use crate::{ enums::AuthInfo, errors::{AnalyticsError, AnalyticsResult}, metrics, - refunds::RefundMetricAccumulator, + refunds::{accumulator::RefundDistributionAccumulator, RefundMetricAccumulator}, AnalyticsProvider, }; +#[derive(Debug)] +pub enum TaskType { + MetricTask( + RefundMetrics, + CustomResult, AnalyticsError>, + ), + DistributionTask( + RefundDistributions, + CustomResult, AnalyticsError>, + ), +} + pub async fn get_metrics( pool: &AnalyticsProvider, + ex_rates: &Option, auth: &AuthInfo, req: GetRefundMetricRequest, -) -> AnalyticsResult> { +) -> AnalyticsResult> { let mut metrics_accumulator: HashMap = HashMap::new(); let mut set = tokio::task::JoinSet::new(); @@ -53,72 +72,206 @@ pub async fn get_metrics( &req.group_by_names.clone(), &auth_scoped, &req.filters, + req.time_series.map(|t| t.granularity), + &req.time_range, + ) + .await + .change_context(AnalyticsError::UnknownError); + TaskType::MetricTask(metric_type, data) + } + .instrument(task_span), + ); + } + + if let Some(distribution) = req.clone().distribution { + let req = req.clone(); + let pool = pool.clone(); + let task_span = tracing::debug_span!( + "analytics_refunds_distribution_query", + refund_distribution = distribution.distribution_for.as_ref() + ); + + let auth_scoped = auth.to_owned(); + set.spawn( + async move { + let data = pool + .get_refund_distribution( + &distribution, + &req.group_by_names.clone(), + &auth_scoped, + &req.filters, &req.time_series.map(|t| t.granularity), &req.time_range, ) .await .change_context(AnalyticsError::UnknownError); - (metric_type, data) + TaskType::DistributionTask(distribution.distribution_for, data) } .instrument(task_span), ); } - while let Some((metric, data)) = set + while let Some(task_type) = set .join_next() .await .transpose() .change_context(AnalyticsError::UnknownError)? { - let data = data?; - let attributes = &add_attributes([ - ("metric_type", metric.to_string()), - ("source", pool.to_string()), - ]); + match task_type { + TaskType::MetricTask(metric, data) => { + let data = data?; + let attributes = router_env::metric_attributes!( + ("metric_type", metric.to_string()), + ("source", pool.to_string()), + ); - let value = u64::try_from(data.len()); - if let Ok(val) = value { - metrics::BUCKETS_FETCHED.record(&metrics::CONTEXT, val, attributes); - logger::debug!("Attributes: {:?}, Buckets fetched: {}", attributes, val); - } + let value = u64::try_from(data.len()); + if let Ok(val) = value { + metrics::BUCKETS_FETCHED.record(val, attributes); + logger::debug!("Attributes: {:?}, Buckets fetched: {}", attributes, val); + } - for (id, value) in data { - logger::debug!(bucket_id=?id, bucket_value=?value, "Bucket row for metric {metric}"); - let metrics_builder = metrics_accumulator.entry(id).or_default(); - match metric { - RefundMetrics::RefundSuccessRate => metrics_builder - .refund_success_rate - .add_metrics_bucket(&value), - RefundMetrics::RefundCount => { - metrics_builder.refund_count.add_metrics_bucket(&value) + for (id, value) in data { + logger::debug!(bucket_id=?id, bucket_value=?value, "Bucket row for metric {metric}"); + let metrics_builder = metrics_accumulator.entry(id).or_default(); + match metric { + RefundMetrics::RefundSuccessRate + | RefundMetrics::SessionizedRefundSuccessRate => metrics_builder + .refund_success_rate + .add_metrics_bucket(&value), + RefundMetrics::RefundCount | RefundMetrics::SessionizedRefundCount => { + metrics_builder.refund_count.add_metrics_bucket(&value) + } + RefundMetrics::RefundSuccessCount + | RefundMetrics::SessionizedRefundSuccessCount => { + metrics_builder.refund_success.add_metrics_bucket(&value) + } + RefundMetrics::RefundProcessedAmount + | RefundMetrics::SessionizedRefundProcessedAmount => { + metrics_builder.processed_amount.add_metrics_bucket(&value) + } + RefundMetrics::SessionizedRefundReason => { + metrics_builder.refund_reason.add_metrics_bucket(&value) + } + RefundMetrics::SessionizedRefundErrorMessage => metrics_builder + .refund_error_message + .add_metrics_bucket(&value), + } } - RefundMetrics::RefundSuccessCount => { - metrics_builder.refund_success.add_metrics_bucket(&value) + + logger::debug!( + "Analytics Accumulated Results: metric: {}, results: {:#?}", + metric, + metrics_accumulator + ); + } + TaskType::DistributionTask(distribution, data) => { + let data = data?; + let attributes = router_env::metric_attributes!( + ("distribution_type", distribution.to_string()), + ("source", pool.to_string()), + ); + let value = u64::try_from(data.len()); + if let Ok(val) = value { + metrics::BUCKETS_FETCHED.record(val, attributes); + logger::debug!("Attributes: {:?}, Buckets fetched: {}", attributes, val); } - RefundMetrics::RefundProcessedAmount => { - metrics_builder.processed_amount.add_metrics_bucket(&value) + + for (id, value) in data { + logger::debug!(bucket_id=?id, bucket_value=?value, "Bucket row for distribution {distribution}"); + + let metrics_builder = metrics_accumulator.entry(id).or_default(); + match distribution { + RefundDistributions::SessionizedRefundReason => metrics_builder + .refund_reason_distribution + .add_distribution_bucket(&value), + RefundDistributions::SessionizedRefundErrorMessage => metrics_builder + .refund_error_message_distribution + .add_distribution_bucket(&value), + } } + logger::debug!( + "Analytics Accumulated Results: distribution: {}, results: {:#?}", + distribution, + metrics_accumulator + ); } } - - logger::debug!( - "Analytics Accumulated Results: metric: {}, results: {:#?}", - metric, - metrics_accumulator - ); } + + let mut success = 0; + let mut total = 0; + let mut total_refund_processed_amount = 0; + let mut total_refund_processed_amount_in_usd = 0; + let mut total_refund_processed_count = 0; + let mut total_refund_reason_count = 0; + let mut total_refund_error_message_count = 0; let query_data: Vec = metrics_accumulator .into_iter() - .map(|(id, val)| RefundMetricsBucketResponse { - values: val.collect(), - dimensions: id, + .map(|(id, val)| { + let mut collected_values = val.collect(); + if let Some(success_count) = collected_values.successful_refunds { + success += success_count; + } + if let Some(total_count) = collected_values.total_refunds { + total += total_count; + } + if let Some(amount) = collected_values.refund_processed_amount { + let amount_in_usd = if let Some(ex_rates) = ex_rates { + id.currency + .and_then(|currency| { + i64::try_from(amount) + .inspect_err(|e| logger::error!("Amount conversion error: {:?}", e)) + .ok() + .and_then(|amount_i64| { + convert(ex_rates, currency, Currency::USD, amount_i64) + .inspect_err(|e| { + logger::error!("Currency conversion error: {:?}", e) + }) + .ok() + }) + }) + .map(|amount| (amount * rust_decimal::Decimal::new(100, 0)).to_u64()) + .unwrap_or_default() + } else { + None + }; + collected_values.refund_processed_amount_in_usd = amount_in_usd; + total_refund_processed_amount += amount; + total_refund_processed_amount_in_usd += amount_in_usd.unwrap_or(0); + } + if let Some(count) = collected_values.refund_processed_count { + total_refund_processed_count += count; + } + if let Some(total_count) = collected_values.refund_reason_count { + total_refund_reason_count += total_count; + } + if let Some(total_count) = collected_values.refund_error_message_count { + total_refund_error_message_count += total_count; + } + RefundMetricsBucketResponse { + values: collected_values, + dimensions: id, + } }) .collect(); - - Ok(MetricsResponse { + let total_refund_success_rate = match (success, total) { + (s, t) if t > 0 => Some(f64::from(s) * 100.0 / f64::from(t)), + _ => None, + }; + Ok(RefundsMetricsResponse { query_data, - meta_data: [AnalyticsMetadata { - current_time_range: req.time_range, + meta_data: [RefundsAnalyticsMetadata { + total_refund_success_rate, + total_refund_processed_amount: Some(total_refund_processed_amount), + total_refund_processed_amount_in_usd: if ex_rates.is_some() { + Some(total_refund_processed_amount_in_usd) + } else { + None + }, + total_refund_processed_count: Some(total_refund_processed_count), + total_refund_reason_count: Some(total_refund_reason_count), + total_refund_error_message_count: Some(total_refund_error_message_count), }], }) } @@ -194,6 +347,8 @@ pub async fn get_filters( RefundDimensions::Connector => fil.connector, RefundDimensions::RefundType => fil.refund_type.map(|i| i.as_ref().to_string()), RefundDimensions::ProfileId => fil.profile_id, + RefundDimensions::RefundReason => fil.refund_reason, + RefundDimensions::RefundErrorMessage => fil.refund_error_message, }) .collect::>(); res.query_data.push(RefundFilterValue { diff --git a/crates/analytics/src/refunds/distribution.rs b/crates/analytics/src/refunds/distribution.rs new file mode 100644 index 000000000000..962f74acd1a1 --- /dev/null +++ b/crates/analytics/src/refunds/distribution.rs @@ -0,0 +1,105 @@ +use api_models::analytics::{ + refunds::{ + RefundDimensions, RefundDistributions, RefundFilters, RefundMetricsBucketIdentifier, + RefundType, + }, + Granularity, RefundDistributionBody, TimeRange, +}; +use diesel_models::enums as storage_enums; +use time::PrimitiveDateTime; + +use crate::{ + enums::AuthInfo, + query::{Aggregate, GroupByClause, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, DBEnumWrapper, LoadRow, MetricsResult}, +}; + +mod sessionized_distribution; + +#[derive(Debug, PartialEq, Eq, serde::Deserialize)] +pub struct RefundDistributionRow { + pub currency: Option>, + pub refund_status: Option>, + pub connector: Option, + pub refund_type: Option>, + pub profile_id: Option, + pub total: Option, + pub count: Option, + pub refund_reason: Option, + pub refund_error_message: Option, + #[serde(with = "common_utils::custom_serde::iso8601::option")] + pub start_bucket: Option, + #[serde(with = "common_utils::custom_serde::iso8601::option")] + pub end_bucket: Option, +} + +pub trait RefundDistributionAnalytics: LoadRow {} + +#[async_trait::async_trait] +pub trait RefundDistribution +where + T: AnalyticsDataSource + RefundDistributionAnalytics, +{ + #[allow(clippy::too_many_arguments)] + async fn load_distribution( + &self, + distribution: &RefundDistributionBody, + dimensions: &[RefundDimensions], + auth: &AuthInfo, + filters: &RefundFilters, + granularity: &Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult>; +} + +#[async_trait::async_trait] +impl RefundDistribution for RefundDistributions +where + T: AnalyticsDataSource + RefundDistributionAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_distribution( + &self, + distribution: &RefundDistributionBody, + dimensions: &[RefundDimensions], + auth: &AuthInfo, + filters: &RefundFilters, + granularity: &Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> { + match self { + Self::SessionizedRefundReason => { + sessionized_distribution::RefundReason + .load_distribution( + distribution, + dimensions, + auth, + filters, + granularity, + time_range, + pool, + ) + .await + } + Self::SessionizedRefundErrorMessage => { + sessionized_distribution::RefundErrorMessage + .load_distribution( + distribution, + dimensions, + auth, + filters, + granularity, + time_range, + pool, + ) + .await + } + } + } +} diff --git a/crates/analytics/src/refunds/distribution/sessionized_distribution.rs b/crates/analytics/src/refunds/distribution/sessionized_distribution.rs new file mode 100644 index 000000000000..391b855e96ea --- /dev/null +++ b/crates/analytics/src/refunds/distribution/sessionized_distribution.rs @@ -0,0 +1,7 @@ +mod refund_error_message; +mod refund_reason; + +pub(super) use refund_error_message::RefundErrorMessage; +pub(super) use refund_reason::RefundReason; + +pub use super::{RefundDistribution, RefundDistributionAnalytics, RefundDistributionRow}; diff --git a/crates/analytics/src/refunds/distribution/sessionized_distribution/refund_error_message.rs b/crates/analytics/src/refunds/distribution/sessionized_distribution/refund_error_message.rs new file mode 100644 index 000000000000..a4268c86cbe2 --- /dev/null +++ b/crates/analytics/src/refunds/distribution/sessionized_distribution/refund_error_message.rs @@ -0,0 +1,177 @@ +use api_models::analytics::{ + refunds::{RefundDimensions, RefundFilters, RefundMetricsBucketIdentifier}, + Granularity, RefundDistributionBody, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use diesel_models::enums as storage_enums; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::{RefundDistribution, RefundDistributionRow}; +use crate::{ + enums::AuthInfo, + query::{ + Aggregate, GroupByClause, Order, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window, + }, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(crate) struct RefundErrorMessage; + +#[async_trait::async_trait] +impl RefundDistribution for RefundErrorMessage +where + T: AnalyticsDataSource + super::RefundDistributionAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_distribution( + &self, + distribution: &RefundDistributionBody, + dimensions: &[RefundDimensions], + auth: &AuthInfo, + filters: &RefundFilters, + granularity: &Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> { + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::RefundSessionized); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + + query_builder + .add_select_column(&distribution.distribution_for) + .switch()?; + + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + query_builder + .add_filter_clause( + RefundDimensions::RefundStatus, + storage_enums::RefundStatus::Failure, + ) + .switch()?; + + for dim in dimensions.iter() { + query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + + query_builder + .add_group_by_clause(&distribution.distribution_for) + .attach_printable("Error grouping by distribution_for") + .switch()?; + + if let Some(granularity) = granularity.as_ref() { + granularity + .set_group_by_clause(&mut query_builder) + .attach_printable("Error adding granularity") + .switch()?; + } + + for dim in dimensions.iter() { + query_builder.add_outer_select_column(dim).switch()?; + } + + query_builder + .add_outer_select_column(&distribution.distribution_for) + .switch()?; + query_builder.add_outer_select_column("count").switch()?; + query_builder + .add_outer_select_column("start_bucket") + .switch()?; + query_builder + .add_outer_select_column("end_bucket") + .switch()?; + let sql_dimensions = query_builder.transform_to_sql_values(dimensions).switch()?; + + query_builder + .add_outer_select_column(Window::Sum { + field: "count", + partition_by: Some(sql_dimensions), + order_by: None, + alias: Some("total"), + }) + .switch()?; + + query_builder + .add_top_n_clause( + dimensions, + distribution.distribution_cardinality.into(), + "count", + Order::Descending, + ) + .switch()?; + + query_builder + .execute_query::(pool) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(|i| { + Ok(( + RefundMetricsBucketIdentifier::new( + i.currency.as_ref().map(|i| i.0), + i.refund_status.as_ref().map(|i| i.0.to_string()), + i.connector.clone(), + i.refund_type.as_ref().map(|i| i.0.to_string()), + i.profile_id.clone(), + i.refund_reason.clone(), + i.refund_error_message.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/refunds/distribution/sessionized_distribution/refund_reason.rs b/crates/analytics/src/refunds/distribution/sessionized_distribution/refund_reason.rs new file mode 100644 index 000000000000..a2a933db8cbe --- /dev/null +++ b/crates/analytics/src/refunds/distribution/sessionized_distribution/refund_reason.rs @@ -0,0 +1,169 @@ +use api_models::analytics::{ + refunds::{RefundDimensions, RefundFilters, RefundMetricsBucketIdentifier}, + Granularity, RefundDistributionBody, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::{RefundDistribution, RefundDistributionRow}; +use crate::{ + enums::AuthInfo, + query::{ + Aggregate, GroupByClause, Order, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window, + }, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(crate) struct RefundReason; + +#[async_trait::async_trait] +impl RefundDistribution for RefundReason +where + T: AnalyticsDataSource + super::RefundDistributionAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_distribution( + &self, + distribution: &RefundDistributionBody, + dimensions: &[RefundDimensions], + auth: &AuthInfo, + filters: &RefundFilters, + granularity: &Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> { + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::RefundSessionized); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + + query_builder + .add_select_column(&distribution.distribution_for) + .switch()?; + + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + + query_builder + .add_group_by_clause(&distribution.distribution_for) + .attach_printable("Error grouping by distribution_for") + .switch()?; + + if let Some(granularity) = granularity.as_ref() { + granularity + .set_group_by_clause(&mut query_builder) + .attach_printable("Error adding granularity") + .switch()?; + } + + for dim in dimensions.iter() { + query_builder.add_outer_select_column(dim).switch()?; + } + + query_builder + .add_outer_select_column(&distribution.distribution_for) + .switch()?; + query_builder.add_outer_select_column("count").switch()?; + query_builder + .add_outer_select_column("start_bucket") + .switch()?; + query_builder + .add_outer_select_column("end_bucket") + .switch()?; + let sql_dimensions = query_builder.transform_to_sql_values(dimensions).switch()?; + + query_builder + .add_outer_select_column(Window::Sum { + field: "count", + partition_by: Some(sql_dimensions), + order_by: None, + alias: Some("total"), + }) + .switch()?; + + query_builder + .add_top_n_clause( + dimensions, + distribution.distribution_cardinality.into(), + "count", + Order::Descending, + ) + .switch()?; + + query_builder + .execute_query::(pool) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(|i| { + Ok(( + RefundMetricsBucketIdentifier::new( + i.currency.as_ref().map(|i| i.0), + i.refund_status.as_ref().map(|i| i.0.to_string()), + i.connector.clone(), + i.refund_type.as_ref().map(|i| i.0.to_string()), + i.profile_id.clone(), + i.refund_reason.clone(), + i.refund_error_message.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/refunds/filters.rs b/crates/analytics/src/refunds/filters.rs index d87a778ebf39..b742187c4e60 100644 --- a/crates/analytics/src/refunds/filters.rs +++ b/crates/analytics/src/refunds/filters.rs @@ -56,4 +56,6 @@ pub struct RefundFilterRow { pub connector: Option, pub refund_type: Option>, pub profile_id: Option, + pub refund_reason: Option, + pub refund_error_message: Option, } diff --git a/crates/analytics/src/refunds/metrics.rs b/crates/analytics/src/refunds/metrics.rs index 6ecfd8aeb293..13990e8c2e62 100644 --- a/crates/analytics/src/refunds/metrics.rs +++ b/crates/analytics/src/refunds/metrics.rs @@ -10,6 +10,7 @@ mod refund_count; mod refund_processed_amount; mod refund_success_count; mod refund_success_rate; +mod sessionized_metrics; use std::collections::HashSet; use refund_count::RefundCount; @@ -30,6 +31,8 @@ pub struct RefundMetricRow { pub connector: Option, pub refund_type: Option>, pub profile_id: Option, + pub refund_reason: Option, + pub refund_error_message: Option, pub total: Option, pub count: Option, #[serde(with = "common_utils::custom_serde::iso8601::option")] @@ -55,7 +58,7 @@ where dimensions: &[RefundDimensions], auth: &AuthInfo, filters: &RefundFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult>; @@ -76,7 +79,7 @@ where dimensions: &[RefundDimensions], auth: &AuthInfo, filters: &RefundFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -101,6 +104,36 @@ where .load_metrics(dimensions, auth, filters, granularity, time_range, pool) .await } + Self::SessionizedRefundSuccessRate => { + sessionized_metrics::RefundSuccessRate::default() + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } + Self::SessionizedRefundCount => { + sessionized_metrics::RefundCount::default() + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } + Self::SessionizedRefundSuccessCount => { + sessionized_metrics::RefundSuccessCount::default() + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } + Self::SessionizedRefundProcessedAmount => { + sessionized_metrics::RefundProcessedAmount::default() + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } + Self::SessionizedRefundReason => { + sessionized_metrics::RefundReason + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } + Self::SessionizedRefundErrorMessage => { + sessionized_metrics::RefundErrorMessage + .load_metrics(dimensions, auth, filters, granularity, time_range, pool) + .await + } } } } diff --git a/crates/analytics/src/refunds/metrics/refund_count.rs b/crates/analytics/src/refunds/metrics/refund_count.rs index 07de04c58983..ae21f166a29f 100644 --- a/crates/analytics/src/refunds/metrics/refund_count.rs +++ b/crates/analytics/src/refunds/metrics/refund_count.rs @@ -33,7 +33,7 @@ where dimensions: &[RefundDimensions], auth: &AuthInfo, filters: &RefundFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -78,7 +78,7 @@ where .switch()?; } - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { granularity .set_group_by_clause(&mut query_builder) .attach_printable("Error adding granularity") @@ -99,6 +99,8 @@ where i.connector.clone(), i.refund_type.as_ref().map(|i| i.0.to_string()), i.profile_id.clone(), + i.refund_reason.clone(), + i.refund_error_message.clone(), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/refunds/metrics/refund_processed_amount.rs b/crates/analytics/src/refunds/metrics/refund_processed_amount.rs index f0f51a21fe0f..9b8c4f313d01 100644 --- a/crates/analytics/src/refunds/metrics/refund_processed_amount.rs +++ b/crates/analytics/src/refunds/metrics/refund_processed_amount.rs @@ -33,7 +33,7 @@ where dimensions: &[RefundDimensions], auth: &AuthInfo, filters: &RefundFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> @@ -52,6 +52,7 @@ where alias: Some("total"), }) .switch()?; + query_builder.add_select_column("currency").switch()?; query_builder .add_select_column(Aggregate::Min { field: "created_at", @@ -78,7 +79,8 @@ where query_builder.add_group_by_clause(dim).switch()?; } - if let Some(granularity) = granularity.as_ref() { + query_builder.add_group_by_clause("currency").switch()?; + if let Some(granularity) = granularity { granularity .set_group_by_clause(&mut query_builder) .switch()?; @@ -105,6 +107,8 @@ where i.connector.clone(), i.refund_type.as_ref().map(|i| i.0.to_string()), i.profile_id.clone(), + i.refund_reason.clone(), + i.refund_error_message.clone(), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/refunds/metrics/refund_success_count.rs b/crates/analytics/src/refunds/metrics/refund_success_count.rs index 642cf70580d4..1eb198687a01 100644 --- a/crates/analytics/src/refunds/metrics/refund_success_count.rs +++ b/crates/analytics/src/refunds/metrics/refund_success_count.rs @@ -34,7 +34,7 @@ where dimensions: &[RefundDimensions], auth: &AuthInfo, filters: &RefundFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> @@ -76,7 +76,7 @@ where query_builder.add_group_by_clause(dim).switch()?; } - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { granularity .set_group_by_clause(&mut query_builder) .switch()?; @@ -102,6 +102,8 @@ where i.connector.clone(), i.refund_type.as_ref().map(|i| i.0.to_string()), i.profile_id.clone(), + i.refund_reason.clone(), + i.refund_error_message.clone(), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/refunds/metrics/refund_success_rate.rs b/crates/analytics/src/refunds/metrics/refund_success_rate.rs index 7b5716ba41a1..c0a8d27db6fc 100644 --- a/crates/analytics/src/refunds/metrics/refund_success_rate.rs +++ b/crates/analytics/src/refunds/metrics/refund_success_rate.rs @@ -32,7 +32,7 @@ where dimensions: &[RefundDimensions], auth: &AuthInfo, filters: &RefundFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> @@ -77,7 +77,7 @@ where query_builder.add_group_by_clause(dim).switch()?; } - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { granularity .set_group_by_clause(&mut query_builder) .switch()?; @@ -97,6 +97,8 @@ where i.connector.clone(), i.refund_type.as_ref().map(|i| i.0.to_string()), i.profile_id.clone(), + i.refund_reason.clone(), + i.refund_error_message.clone(), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/refunds/metrics/sessionized_metrics.rs b/crates/analytics/src/refunds/metrics/sessionized_metrics.rs new file mode 100644 index 000000000000..3a5195be6c9e --- /dev/null +++ b/crates/analytics/src/refunds/metrics/sessionized_metrics.rs @@ -0,0 +1,15 @@ +mod refund_count; +mod refund_error_message; +mod refund_processed_amount; +mod refund_reason; +mod refund_success_count; +mod refund_success_rate; + +pub(super) use refund_count::RefundCount; +pub(super) use refund_error_message::RefundErrorMessage; +pub(super) use refund_processed_amount::RefundProcessedAmount; +pub(super) use refund_reason::RefundReason; +pub(super) use refund_success_count::RefundSuccessCount; +pub(super) use refund_success_rate::RefundSuccessRate; + +pub use super::{RefundMetric, RefundMetricAnalytics, RefundMetricRow}; diff --git a/crates/analytics/src/refunds/metrics/sessionized_metrics/refund_count.rs b/crates/analytics/src/refunds/metrics/sessionized_metrics/refund_count.rs new file mode 100644 index 000000000000..c7114e5ddc93 --- /dev/null +++ b/crates/analytics/src/refunds/metrics/sessionized_metrics/refund_count.rs @@ -0,0 +1,122 @@ +use std::collections::HashSet; + +use api_models::analytics::{ + refunds::{RefundDimensions, RefundFilters, RefundMetricsBucketIdentifier}, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::RefundMetricRow; +use crate::{ + enums::AuthInfo, + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(crate) struct RefundCount {} + +#[async_trait::async_trait] +impl super::RefundMetric for RefundCount +where + T: AnalyticsDataSource + super::RefundMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[RefundDimensions], + auth: &AuthInfo, + filters: &RefundFilters, + granularity: Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> { + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::RefundSessionized); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + + if let Some(granularity) = granularity { + granularity + .set_group_by_clause(&mut query_builder) + .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(( + RefundMetricsBucketIdentifier::new( + i.currency.as_ref().map(|i| i.0), + i.refund_status.as_ref().map(|i| i.0.to_string()), + i.connector.clone(), + i.refund_type.as_ref().map(|i| i.0.to_string()), + i.profile_id.clone(), + i.refund_reason.clone(), + i.refund_error_message.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, crate::query::PostProcessingError>>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/refunds/metrics/sessionized_metrics/refund_error_message.rs b/crates/analytics/src/refunds/metrics/sessionized_metrics/refund_error_message.rs new file mode 100644 index 000000000000..c6579005fac4 --- /dev/null +++ b/crates/analytics/src/refunds/metrics/sessionized_metrics/refund_error_message.rs @@ -0,0 +1,190 @@ +use std::collections::HashSet; + +use api_models::analytics::{ + refunds::{RefundDimensions, RefundFilters, RefundMetricsBucketIdentifier}, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use diesel_models::enums as storage_enums; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::RefundMetricRow; +use crate::{ + enums::AuthInfo, + query::{ + Aggregate, FilterTypes, GroupByClause, Order, QueryBuilder, QueryFilter, SeriesBucket, + ToSql, Window, + }, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(crate) struct RefundErrorMessage; + +#[async_trait::async_trait] +impl super::RefundMetric for RefundErrorMessage +where + T: AnalyticsDataSource + super::RefundMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[RefundDimensions], + auth: &AuthInfo, + filters: &RefundFilters, + granularity: Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> { + let mut inner_query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::RefundSessionized); + inner_query_builder + .add_select_column("sum(sign_flag)") + .switch()?; + + inner_query_builder + .add_custom_filter_clause( + RefundDimensions::RefundErrorMessage, + "NULL", + FilterTypes::IsNotNull, + ) + .switch()?; + + time_range + .set_filter_clause(&mut inner_query_builder) + .attach_printable("Error filtering time range for inner query") + .switch()?; + + let inner_query_string = inner_query_builder + .build_query() + .attach_printable("Error building inner query") + .change_context(MetricsError::QueryBuildingError)?; + + let mut outer_query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::RefundSessionized); + + for dim in dimensions.iter() { + outer_query_builder.add_select_column(dim).switch()?; + } + + outer_query_builder + .add_select_column("sum(sign_flag) AS count") + .switch()?; + + outer_query_builder + .add_select_column(format!("({}) AS total", inner_query_string)) + .switch()?; + + outer_query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + + outer_query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters + .set_filter_clause(&mut outer_query_builder) + .switch()?; + + auth.set_filter_clause(&mut outer_query_builder).switch()?; + + time_range + .set_filter_clause(&mut outer_query_builder) + .attach_printable("Error filtering time range for outer query") + .switch()?; + + outer_query_builder + .add_filter_clause( + RefundDimensions::RefundStatus, + storage_enums::RefundStatus::Failure, + ) + .switch()?; + + outer_query_builder + .add_custom_filter_clause( + RefundDimensions::RefundErrorMessage, + "NULL", + FilterTypes::IsNotNull, + ) + .switch()?; + + for dim in dimensions.iter() { + outer_query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + + if let Some(granularity) = granularity { + granularity + .set_group_by_clause(&mut outer_query_builder) + .attach_printable("Error adding granularity") + .switch()?; + } + + outer_query_builder + .add_order_by_clause("count", Order::Descending) + .attach_printable("Error adding order by clause") + .switch()?; + + let filtered_dimensions: Vec<&RefundDimensions> = dimensions + .iter() + .filter(|&&dim| dim != RefundDimensions::RefundErrorMessage) + .collect(); + + for dim in &filtered_dimensions { + outer_query_builder + .add_order_by_clause(*dim, Order::Ascending) + .attach_printable("Error adding order by clause") + .switch()?; + } + + outer_query_builder + .execute_query::(pool) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(|i| { + Ok(( + RefundMetricsBucketIdentifier::new( + i.currency.as_ref().map(|i| i.0), + None, + i.connector.clone(), + i.refund_type.as_ref().map(|i| i.0.to_string()), + i.profile_id.clone(), + i.refund_reason.clone(), + i.refund_error_message.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/refunds/metrics/sessionized_metrics/refund_processed_amount.rs b/crates/analytics/src/refunds/metrics/sessionized_metrics/refund_processed_amount.rs new file mode 100644 index 000000000000..b376b7c12863 --- /dev/null +++ b/crates/analytics/src/refunds/metrics/sessionized_metrics/refund_processed_amount.rs @@ -0,0 +1,137 @@ +use std::collections::HashSet; + +use api_models::analytics::{ + refunds::{RefundDimensions, RefundFilters, RefundMetricsBucketIdentifier}, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use diesel_models::enums as storage_enums; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::RefundMetricRow; +use crate::{ + enums::AuthInfo, + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; +#[derive(Default)] +pub(crate) struct RefundProcessedAmount {} + +#[async_trait::async_trait] +impl super::RefundMetric for RefundProcessedAmount +where + T: AnalyticsDataSource + super::RefundMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[RefundDimensions], + auth: &AuthInfo, + filters: &RefundFilters, + granularity: Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> + where + T: AnalyticsDataSource + super::RefundMetricAnalytics, + { + let mut query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::RefundSessionized); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Sum { + field: "refund_amount", + alias: Some("total"), + }) + .switch()?; + query_builder.add_select_column("currency").switch()?; + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + time_range + .set_filter_clause(&mut query_builder) + .attach_printable("Error filtering time range") + .switch()?; + + for dim in dimensions.iter() { + query_builder.add_group_by_clause(dim).switch()?; + } + + query_builder.add_group_by_clause("currency").switch()?; + + if let Some(granularity) = granularity { + granularity + .set_group_by_clause(&mut query_builder) + .switch()?; + } + + query_builder + .add_filter_clause( + RefundDimensions::RefundStatus, + storage_enums::RefundStatus::Success, + ) + .switch()?; + + query_builder + .execute_query::(pool) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(|i| { + Ok(( + RefundMetricsBucketIdentifier::new( + i.currency.as_ref().map(|i| i.0), + None, + i.connector.clone(), + i.refund_type.as_ref().map(|i| i.0.to_string()), + i.profile_id.clone(), + i.refund_reason.clone(), + i.refund_error_message.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, crate::query::PostProcessingError>>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/refunds/metrics/sessionized_metrics/refund_reason.rs b/crates/analytics/src/refunds/metrics/sessionized_metrics/refund_reason.rs new file mode 100644 index 000000000000..f7a2e11676f4 --- /dev/null +++ b/crates/analytics/src/refunds/metrics/sessionized_metrics/refund_reason.rs @@ -0,0 +1,182 @@ +use std::collections::HashSet; + +use api_models::analytics::{ + refunds::{RefundDimensions, RefundFilters, RefundMetricsBucketIdentifier}, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::RefundMetricRow; +use crate::{ + enums::AuthInfo, + query::{ + Aggregate, FilterTypes, GroupByClause, Order, QueryBuilder, QueryFilter, SeriesBucket, + ToSql, Window, + }, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(crate) struct RefundReason; + +#[async_trait::async_trait] +impl super::RefundMetric for RefundReason +where + T: AnalyticsDataSource + super::RefundMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[RefundDimensions], + auth: &AuthInfo, + filters: &RefundFilters, + granularity: Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> { + let mut inner_query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::RefundSessionized); + inner_query_builder + .add_select_column("sum(sign_flag)") + .switch()?; + + inner_query_builder + .add_custom_filter_clause( + RefundDimensions::RefundReason, + "NULL", + FilterTypes::IsNotNull, + ) + .switch()?; + + time_range + .set_filter_clause(&mut inner_query_builder) + .attach_printable("Error filtering time range for inner query") + .switch()?; + + let inner_query_string = inner_query_builder + .build_query() + .attach_printable("Error building inner query") + .change_context(MetricsError::QueryBuildingError)?; + + let mut outer_query_builder: QueryBuilder = + QueryBuilder::new(AnalyticsCollection::RefundSessionized); + + for dim in dimensions.iter() { + outer_query_builder.add_select_column(dim).switch()?; + } + + outer_query_builder + .add_select_column("sum(sign_flag) AS count") + .switch()?; + + outer_query_builder + .add_select_column(format!("({}) AS total", inner_query_string)) + .switch()?; + + outer_query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + + outer_query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters + .set_filter_clause(&mut outer_query_builder) + .switch()?; + + auth.set_filter_clause(&mut outer_query_builder).switch()?; + + time_range + .set_filter_clause(&mut outer_query_builder) + .attach_printable("Error filtering time range for outer query") + .switch()?; + + outer_query_builder + .add_custom_filter_clause( + RefundDimensions::RefundReason, + "NULL", + FilterTypes::IsNotNull, + ) + .switch()?; + + for dim in dimensions.iter() { + outer_query_builder + .add_group_by_clause(dim) + .attach_printable("Error grouping by dimensions") + .switch()?; + } + + if let Some(granularity) = granularity { + granularity + .set_group_by_clause(&mut outer_query_builder) + .attach_printable("Error adding granularity") + .switch()?; + } + + outer_query_builder + .add_order_by_clause("count", Order::Descending) + .attach_printable("Error adding order by clause") + .switch()?; + + let filtered_dimensions: Vec<&RefundDimensions> = dimensions + .iter() + .filter(|&&dim| dim != RefundDimensions::RefundReason) + .collect(); + + for dim in &filtered_dimensions { + outer_query_builder + .add_order_by_clause(*dim, Order::Ascending) + .attach_printable("Error adding order by clause") + .switch()?; + } + + outer_query_builder + .execute_query::(pool) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(|i| { + Ok(( + RefundMetricsBucketIdentifier::new( + i.currency.as_ref().map(|i| i.0), + None, + i.connector.clone(), + i.refund_type.as_ref().map(|i| i.0.to_string()), + i.profile_id.clone(), + i.refund_reason.clone(), + i.refund_error_message.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/refunds/metrics/sessionized_metrics/refund_success_count.rs b/crates/analytics/src/refunds/metrics/sessionized_metrics/refund_success_count.rs new file mode 100644 index 000000000000..ce9c8f4b397c --- /dev/null +++ b/crates/analytics/src/refunds/metrics/sessionized_metrics/refund_success_count.rs @@ -0,0 +1,127 @@ +use std::collections::HashSet; + +use api_models::analytics::{ + refunds::{RefundDimensions, RefundFilters, RefundMetricsBucketIdentifier}, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use diesel_models::enums as storage_enums; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::RefundMetricRow; +use crate::{ + enums::AuthInfo, + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; + +#[derive(Default)] +pub(crate) struct RefundSuccessCount {} + +#[async_trait::async_trait] +impl super::RefundMetric for RefundSuccessCount +where + T: AnalyticsDataSource + super::RefundMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[RefundDimensions], + auth: &AuthInfo, + filters: &RefundFilters, + granularity: Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> + where + T: AnalyticsDataSource + super::RefundMetricAnalytics, + { + let mut query_builder = QueryBuilder::new(AnalyticsCollection::RefundSessionized); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + time_range.set_filter_clause(&mut query_builder).switch()?; + + for dim in dimensions.iter() { + query_builder.add_group_by_clause(dim).switch()?; + } + + if let Some(granularity) = granularity { + granularity + .set_group_by_clause(&mut query_builder) + .switch()?; + } + + query_builder + .add_filter_clause( + RefundDimensions::RefundStatus, + storage_enums::RefundStatus::Success, + ) + .switch()?; + query_builder + .execute_query::(pool) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(|i| { + Ok(( + RefundMetricsBucketIdentifier::new( + i.currency.as_ref().map(|i| i.0), + None, + i.connector.clone(), + i.refund_type.as_ref().map(|i| i.0.to_string()), + i.profile_id.clone(), + i.refund_reason.clone(), + i.refund_error_message.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/refunds/metrics/sessionized_metrics/refund_success_rate.rs b/crates/analytics/src/refunds/metrics/sessionized_metrics/refund_success_rate.rs new file mode 100644 index 000000000000..b04698c28d0f --- /dev/null +++ b/crates/analytics/src/refunds/metrics/sessionized_metrics/refund_success_rate.rs @@ -0,0 +1,122 @@ +use std::collections::HashSet; + +use api_models::analytics::{ + refunds::{RefundDimensions, RefundFilters, RefundMetricsBucketIdentifier}, + Granularity, TimeRange, +}; +use common_utils::errors::ReportSwitchExt; +use error_stack::ResultExt; +use time::PrimitiveDateTime; + +use super::RefundMetricRow; +use crate::{ + enums::AuthInfo, + query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window}, + types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult}, +}; +#[derive(Default)] +pub(crate) struct RefundSuccessRate {} + +#[async_trait::async_trait] +impl super::RefundMetric for RefundSuccessRate +where + T: AnalyticsDataSource + super::RefundMetricAnalytics, + PrimitiveDateTime: ToSql, + AnalyticsCollection: ToSql, + Granularity: GroupByClause, + Aggregate<&'static str>: ToSql, + Window<&'static str>: ToSql, +{ + async fn load_metrics( + &self, + dimensions: &[RefundDimensions], + auth: &AuthInfo, + filters: &RefundFilters, + granularity: Option, + time_range: &TimeRange, + pool: &T, + ) -> MetricsResult> + where + T: AnalyticsDataSource + super::RefundMetricAnalytics, + { + let mut query_builder = QueryBuilder::new(AnalyticsCollection::RefundSessionized); + let mut dimensions = dimensions.to_vec(); + + dimensions.push(RefundDimensions::RefundStatus); + + for dim in dimensions.iter() { + query_builder.add_select_column(dim).switch()?; + } + + query_builder + .add_select_column(Aggregate::Count { + field: None, + alias: Some("count"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Min { + field: "created_at", + alias: Some("start_bucket"), + }) + .switch()?; + query_builder + .add_select_column(Aggregate::Max { + field: "created_at", + alias: Some("end_bucket"), + }) + .switch()?; + + filters.set_filter_clause(&mut query_builder).switch()?; + + auth.set_filter_clause(&mut query_builder).switch()?; + + time_range.set_filter_clause(&mut query_builder).switch()?; + + for dim in dimensions.iter() { + query_builder.add_group_by_clause(dim).switch()?; + } + + if let Some(granularity) = granularity { + granularity + .set_group_by_clause(&mut query_builder) + .switch()?; + } + + query_builder + .execute_query::(pool) + .await + .change_context(MetricsError::QueryBuildingError)? + .change_context(MetricsError::QueryExecutionFailure)? + .into_iter() + .map(|i| { + Ok(( + RefundMetricsBucketIdentifier::new( + i.currency.as_ref().map(|i| i.0), + None, + i.connector.clone(), + i.refund_type.as_ref().map(|i| i.0.to_string()), + i.profile_id.clone(), + i.refund_reason.clone(), + i.refund_error_message.clone(), + TimeRange { + start_time: match (granularity, i.start_bucket) { + (Some(g), Some(st)) => g.clip_to_start(st)?, + _ => time_range.start_time, + }, + end_time: granularity.as_ref().map_or_else( + || Ok(time_range.end_time), + |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(), + )?, + }, + ), + i, + )) + }) + .collect::, + crate::query::PostProcessingError, + >>() + .change_context(MetricsError::PostProcessingFailure) + } +} diff --git a/crates/analytics/src/refunds/types.rs b/crates/analytics/src/refunds/types.rs index 3f22081a69d1..c0735557d1ce 100644 --- a/crates/analytics/src/refunds/types.rs +++ b/crates/analytics/src/refunds/types.rs @@ -42,6 +42,21 @@ where .attach_printable("Error adding profile id filter")?; } + if !self.refund_reason.is_empty() { + builder + .add_filter_in_range_clause(RefundDimensions::RefundReason, &self.refund_reason) + .attach_printable("Error adding refund reason filter")?; + } + + if !self.refund_error_message.is_empty() { + builder + .add_filter_in_range_clause( + RefundDimensions::RefundErrorMessage, + &self.refund_error_message, + ) + .attach_printable("Error adding refund error message filter")?; + } + Ok(()) } } diff --git a/crates/analytics/src/sdk_events/core.rs b/crates/analytics/src/sdk_events/core.rs index 3eca479818b2..c8ea156674a2 100644 --- a/crates/analytics/src/sdk_events/core.rs +++ b/crates/analytics/src/sdk_events/core.rs @@ -65,7 +65,7 @@ pub async fn get_metrics( &req.group_by_names.clone(), &publishable_key_scoped, &req.filters, - &req.time_series.map(|t| t.granularity), + req.time_series.map(|t| t.granularity), &req.time_range, ) .await diff --git a/crates/analytics/src/sdk_events/metrics.rs b/crates/analytics/src/sdk_events/metrics.rs index 7d5ad0c53d48..3c587925a2fc 100644 --- a/crates/analytics/src/sdk_events/metrics.rs +++ b/crates/analytics/src/sdk_events/metrics.rs @@ -56,7 +56,7 @@ where dimensions: &[SdkEventDimensions], publishable_key: &str, filters: &SdkEventFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult>; @@ -77,7 +77,7 @@ where dimensions: &[SdkEventDimensions], publishable_key: &str, filters: &SdkEventFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { diff --git a/crates/analytics/src/sdk_events/metrics/average_payment_time.rs b/crates/analytics/src/sdk_events/metrics/average_payment_time.rs index c7f6bca988c7..1b3ce5668270 100644 --- a/crates/analytics/src/sdk_events/metrics/average_payment_time.rs +++ b/crates/analytics/src/sdk_events/metrics/average_payment_time.rs @@ -34,7 +34,7 @@ where dimensions: &[SdkEventDimensions], publishable_key: &str, filters: &SdkEventFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -54,7 +54,7 @@ where }) .switch()?; - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { query_builder .add_granularity_in_mins(granularity) .switch()?; diff --git a/crates/analytics/src/sdk_events/metrics/load_time.rs b/crates/analytics/src/sdk_events/metrics/load_time.rs index 73cc693c5064..6c6ce21a75e5 100644 --- a/crates/analytics/src/sdk_events/metrics/load_time.rs +++ b/crates/analytics/src/sdk_events/metrics/load_time.rs @@ -34,7 +34,7 @@ where dimensions: &[SdkEventDimensions], publishable_key: &str, filters: &SdkEventFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -54,7 +54,7 @@ where }) .switch()?; - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { query_builder .add_granularity_in_mins(granularity) .switch()?; diff --git a/crates/analytics/src/sdk_events/metrics/payment_attempts.rs b/crates/analytics/src/sdk_events/metrics/payment_attempts.rs index 4d949d9fb822..a7bb717102f5 100644 --- a/crates/analytics/src/sdk_events/metrics/payment_attempts.rs +++ b/crates/analytics/src/sdk_events/metrics/payment_attempts.rs @@ -34,7 +34,7 @@ where dimensions: &[SdkEventDimensions], publishable_key: &str, filters: &SdkEventFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -53,7 +53,7 @@ where }) .switch()?; - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { query_builder .add_granularity_in_mins(granularity) .switch()?; diff --git a/crates/analytics/src/sdk_events/metrics/payment_data_filled_count.rs b/crates/analytics/src/sdk_events/metrics/payment_data_filled_count.rs index 37eb967b385b..0488907f9a77 100644 --- a/crates/analytics/src/sdk_events/metrics/payment_data_filled_count.rs +++ b/crates/analytics/src/sdk_events/metrics/payment_data_filled_count.rs @@ -34,7 +34,7 @@ where dimensions: &[SdkEventDimensions], publishable_key: &str, filters: &SdkEventFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -53,7 +53,7 @@ where }) .switch()?; - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { query_builder .add_granularity_in_mins(granularity) .switch()?; diff --git a/crates/analytics/src/sdk_events/metrics/payment_method_selected_count.rs b/crates/analytics/src/sdk_events/metrics/payment_method_selected_count.rs index d524d0021cf5..9599caa1e7b7 100644 --- a/crates/analytics/src/sdk_events/metrics/payment_method_selected_count.rs +++ b/crates/analytics/src/sdk_events/metrics/payment_method_selected_count.rs @@ -34,7 +34,7 @@ where dimensions: &[SdkEventDimensions], publishable_key: &str, filters: &SdkEventFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -53,7 +53,7 @@ where }) .switch()?; - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { query_builder .add_granularity_in_mins(granularity) .switch()?; diff --git a/crates/analytics/src/sdk_events/metrics/payment_methods_call_count.rs b/crates/analytics/src/sdk_events/metrics/payment_methods_call_count.rs index 081c4968c533..77e6f3899305 100644 --- a/crates/analytics/src/sdk_events/metrics/payment_methods_call_count.rs +++ b/crates/analytics/src/sdk_events/metrics/payment_methods_call_count.rs @@ -34,7 +34,7 @@ where dimensions: &[SdkEventDimensions], publishable_key: &str, filters: &SdkEventFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -53,7 +53,7 @@ where }) .switch()?; - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { query_builder .add_granularity_in_mins(granularity) .switch()?; diff --git a/crates/analytics/src/sdk_events/metrics/sdk_initiated_count.rs b/crates/analytics/src/sdk_events/metrics/sdk_initiated_count.rs index 92308acac692..25c81eacd806 100644 --- a/crates/analytics/src/sdk_events/metrics/sdk_initiated_count.rs +++ b/crates/analytics/src/sdk_events/metrics/sdk_initiated_count.rs @@ -34,7 +34,7 @@ where dimensions: &[SdkEventDimensions], publishable_key: &str, filters: &SdkEventFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -53,7 +53,7 @@ where }) .switch()?; - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { query_builder .add_granularity_in_mins(granularity) .switch()?; diff --git a/crates/analytics/src/sdk_events/metrics/sdk_rendered_count.rs b/crates/analytics/src/sdk_events/metrics/sdk_rendered_count.rs index 6f03008e0c19..e972abbd8e36 100644 --- a/crates/analytics/src/sdk_events/metrics/sdk_rendered_count.rs +++ b/crates/analytics/src/sdk_events/metrics/sdk_rendered_count.rs @@ -34,7 +34,7 @@ where dimensions: &[SdkEventDimensions], publishable_key: &str, filters: &SdkEventFilters, - granularity: &Option, + granularity: Option, time_range: &TimeRange, pool: &T, ) -> MetricsResult> { @@ -53,7 +53,7 @@ where }) .switch()?; - if let Some(granularity) = granularity.as_ref() { + if let Some(granularity) = granularity { query_builder .add_granularity_in_mins(granularity) .switch()?; diff --git a/crates/analytics/src/search.rs b/crates/analytics/src/search.rs index c81ff2f416bf..9be0200030d2 100644 --- a/crates/analytics/src/search.rs +++ b/crates/analytics/src/search.rs @@ -92,6 +92,44 @@ pub async fn msearch_results( .switch()?; } }; + if let Some(connector) = filters.connector { + if !connector.is_empty() { + query_builder + .add_filter_clause("connector.keyword".to_string(), connector.clone()) + .switch()?; + } + }; + if let Some(payment_method_type) = filters.payment_method_type { + if !payment_method_type.is_empty() { + query_builder + .add_filter_clause( + "payment_method_type.keyword".to_string(), + payment_method_type.clone(), + ) + .switch()?; + } + }; + if let Some(card_network) = filters.card_network { + if !card_network.is_empty() { + query_builder + .add_filter_clause("card_network.keyword".to_string(), card_network.clone()) + .switch()?; + } + }; + if let Some(card_last_4) = filters.card_last_4 { + if !card_last_4.is_empty() { + query_builder + .add_filter_clause("card_last_4.keyword".to_string(), card_last_4.clone()) + .switch()?; + } + }; + if let Some(payment_id) = filters.payment_id { + if !payment_id.is_empty() { + query_builder + .add_filter_clause("payment_id.keyword".to_string(), payment_id.clone()) + .switch()?; + } + }; }; if let Some(time_range) = req.time_range { @@ -152,7 +190,17 @@ pub async fn search_results( search_params: Vec, ) -> CustomResult { let search_req = req.search_req; - + if search_req.query.trim().is_empty() + && search_req + .filters + .as_ref() + .map_or(true, |filters| filters.is_all_none()) + { + return Err(OpenSearchError::BadRequestError( + "Both query and filters are empty".to_string(), + ) + .into()); + } let mut query_builder = OpenSearchQueryBuilder::new( OpenSearchQuery::Search(req.index), search_req.query, @@ -217,6 +265,44 @@ pub async fn search_results( .switch()?; } }; + if let Some(connector) = filters.connector { + if !connector.is_empty() { + query_builder + .add_filter_clause("connector.keyword".to_string(), connector.clone()) + .switch()?; + } + }; + if let Some(payment_method_type) = filters.payment_method_type { + if !payment_method_type.is_empty() { + query_builder + .add_filter_clause( + "payment_method_type.keyword".to_string(), + payment_method_type.clone(), + ) + .switch()?; + } + }; + if let Some(card_network) = filters.card_network { + if !card_network.is_empty() { + query_builder + .add_filter_clause("card_network.keyword".to_string(), card_network.clone()) + .switch()?; + } + }; + if let Some(card_last_4) = filters.card_last_4 { + if !card_last_4.is_empty() { + query_builder + .add_filter_clause("card_last_4.keyword".to_string(), card_last_4.clone()) + .switch()?; + } + }; + if let Some(payment_id) = filters.payment_id { + if !payment_id.is_empty() { + query_builder + .add_filter_clause("payment_id.keyword".to_string(), payment_id.clone()) + .switch()?; + } + }; }; if let Some(time_range) = search_req.time_range { diff --git a/crates/analytics/src/sqlx.rs b/crates/analytics/src/sqlx.rs index e93954392097..2a94d528768d 100644 --- a/crates/analytics/src/sqlx.rs +++ b/crates/analytics/src/sqlx.rs @@ -154,6 +154,7 @@ impl super::payment_intents::filters::PaymentIntentFilterAnalytics for SqlxClien impl super::payment_intents::metrics::PaymentIntentMetricAnalytics for SqlxClient {} impl super::refunds::metrics::RefundMetricAnalytics for SqlxClient {} impl super::refunds::filters::RefundFilterAnalytics for SqlxClient {} +impl super::refunds::distribution::RefundDistributionAnalytics for SqlxClient {} impl super::disputes::filters::DisputeFilterAnalytics for SqlxClient {} impl super::disputes::metrics::DisputeMetricAnalytics for SqlxClient {} impl super::frm::metrics::FrmMetricAnalytics for SqlxClient {} @@ -214,6 +215,15 @@ impl<'a> FromRow<'a, PgRow> for super::refunds::metrics::RefundMetricRow { ColumnNotFound(_) => Ok(Default::default()), e => Err(e), })?; + let refund_reason: Option = row.try_get("refund_reason").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let refund_error_message: Option = + row.try_get("refund_error_message").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), @@ -235,6 +245,8 @@ impl<'a> FromRow<'a, PgRow> for super::refunds::metrics::RefundMetricRow { connector, refund_type, profile_id, + refund_reason, + refund_error_message, total, count, start_bucket, @@ -330,6 +342,30 @@ impl<'a> FromRow<'a, PgRow> for super::payments::metrics::PaymentMetricRow { ColumnNotFound(_) => Ok(Default::default()), e => Err(e), })?; + let card_network: Option = row.try_get("card_network").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let merchant_id: Option = row.try_get("merchant_id").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let card_last_4: Option = row.try_get("card_last_4").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let card_issuer: Option = row.try_get("card_issuer").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let error_reason: Option = row.try_get("error_reason").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let first_attempt: Option = row.try_get("first_attempt").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), @@ -355,6 +391,12 @@ impl<'a> FromRow<'a, PgRow> for super::payments::metrics::PaymentMetricRow { client_source, client_version, profile_id, + card_network, + merchant_id, + card_last_4, + card_issuer, + error_reason, + first_attempt, total, count, start_bucket, @@ -407,6 +449,26 @@ impl<'a> FromRow<'a, PgRow> for super::payments::distribution::PaymentDistributi ColumnNotFound(_) => Ok(Default::default()), e => Err(e), })?; + let card_network: Option = row.try_get("card_network").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let merchant_id: Option = row.try_get("merchant_id").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let card_last_4: Option = row.try_get("card_last_4").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let card_issuer: Option = row.try_get("card_issuer").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let error_reason: Option = row.try_get("error_reason").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), @@ -419,6 +481,10 @@ impl<'a> FromRow<'a, PgRow> for super::payments::distribution::PaymentDistributi ColumnNotFound(_) => Ok(Default::default()), e => Err(e), })?; + let first_attempt: Option = row.try_get("first_attempt").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; // Removing millisecond precision to get accurate diffs against clickhouse let start_bucket: Option = row .try_get::, _>("start_bucket")? @@ -436,6 +502,12 @@ impl<'a> FromRow<'a, PgRow> for super::payments::distribution::PaymentDistributi client_source, client_version, profile_id, + card_network, + merchant_id, + card_last_4, + card_issuer, + error_reason, + first_attempt, total, count, error_message, @@ -493,6 +565,26 @@ impl<'a> FromRow<'a, PgRow> for super::payments::filters::PaymentFilterRow { ColumnNotFound(_) => Ok(Default::default()), e => Err(e), })?; + let merchant_id: Option = row.try_get("merchant_id").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let card_last_4: Option = row.try_get("card_last_4").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let card_issuer: Option = row.try_get("card_issuer").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let error_reason: Option = row.try_get("error_reason").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let first_attempt: Option = row.try_get("first_attempt").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; Ok(Self { currency, status, @@ -504,6 +596,11 @@ impl<'a> FromRow<'a, PgRow> for super::payments::filters::PaymentFilterRow { client_version, profile_id, card_network, + merchant_id, + card_last_4, + card_issuer, + error_reason, + first_attempt, }) } } @@ -524,6 +621,45 @@ impl<'a> FromRow<'a, PgRow> for super::payment_intents::metrics::PaymentIntentMe ColumnNotFound(_) => Ok(Default::default()), e => Err(e), })?; + let connector: Option = row.try_get("connector").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let authentication_type: Option> = + row.try_get("authentication_type").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let payment_method: Option = + row.try_get("payment_method").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let payment_method_type: Option = + row.try_get("payment_method_type").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let card_network: Option = row.try_get("card_network").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let merchant_id: Option = row.try_get("merchant_id").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let card_last_4: Option = row.try_get("card_last_4").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let card_issuer: Option = row.try_get("card_issuer").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let error_reason: Option = row.try_get("error_reason").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), @@ -532,6 +668,10 @@ impl<'a> FromRow<'a, PgRow> for super::payment_intents::metrics::PaymentIntentMe ColumnNotFound(_) => Ok(Default::default()), e => Err(e), })?; + let first_attempt: Option = row.try_get("first_attempt").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; // Removing millisecond precision to get accurate diffs against clickhouse let start_bucket: Option = row .try_get::, _>("start_bucket")? @@ -543,6 +683,16 @@ impl<'a> FromRow<'a, PgRow> for super::payment_intents::metrics::PaymentIntentMe status, currency, profile_id, + connector, + authentication_type, + payment_method, + payment_method_type, + card_network, + merchant_id, + card_last_4, + card_issuer, + error_reason, + first_attempt, total, count, start_bucket, @@ -567,11 +717,63 @@ impl<'a> FromRow<'a, PgRow> for super::payment_intents::filters::PaymentIntentFi ColumnNotFound(_) => Ok(Default::default()), e => Err(e), })?; - + let connector: Option = row.try_get("connector").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let authentication_type: Option> = + row.try_get("authentication_type").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let payment_method: Option = + row.try_get("payment_method").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let payment_method_type: Option = + row.try_get("payment_method_type").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let card_network: Option = row.try_get("card_network").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let merchant_id: Option = row.try_get("merchant_id").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let card_last_4: Option = row.try_get("card_last_4").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let card_issuer: Option = row.try_get("card_issuer").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let error_reason: Option = row.try_get("error_reason").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let customer_id: Option = row.try_get("customer_id").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; Ok(Self { status, currency, profile_id, + connector, + authentication_type, + payment_method, + payment_method_type, + card_network, + merchant_id, + card_last_4, + card_issuer, + error_reason, + customer_id, }) } } @@ -601,12 +803,88 @@ impl<'a> FromRow<'a, PgRow> for super::refunds::filters::RefundFilterRow { ColumnNotFound(_) => Ok(Default::default()), e => Err(e), })?; + let refund_reason: Option = row.try_get("refund_reason").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let refund_error_message: Option = + row.try_get("refund_error_message").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + Ok(Self { + currency, + refund_status, + connector, + refund_type, + profile_id, + refund_reason, + refund_error_message, + }) + } +} + +impl<'a> FromRow<'a, PgRow> for super::refunds::distribution::RefundDistributionRow { + fn from_row(row: &'a PgRow) -> sqlx::Result { + let currency: Option> = + row.try_get("currency").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let refund_status: Option> = + row.try_get("refund_status").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let connector: Option = row.try_get("connector").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let refund_type: Option> = + row.try_get("refund_type").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let profile_id: Option = row.try_get("profile_id").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), + })?; + let count: Option = row.try_get("count").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let refund_reason: Option = row.try_get("refund_reason").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + let refund_error_message: Option = + row.try_get("refund_error_message").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; + // Removing millisecond precision to get accurate diffs against clickhouse + let start_bucket: Option = row + .try_get::, _>("start_bucket")? + .and_then(|dt| dt.replace_millisecond(0).ok()); + let end_bucket: Option = row + .try_get::, _>("end_bucket")? + .and_then(|dt| dt.replace_millisecond(0).ok()); Ok(Self { currency, refund_status, connector, refund_type, profile_id, + total, + count, + refund_reason, + refund_error_message, + start_bucket, + end_bucket, }) } } @@ -716,7 +994,11 @@ impl ToSql for AnalyticsCollection { fn to_sql(&self, _table_engine: &TableEngine) -> error_stack::Result { match self { Self::Payment => Ok("payment_attempt".to_string()), + Self::PaymentSessionized => Err(error_stack::report!(ParsingError::UnknownError) + .attach_printable("PaymentSessionized table is not implemented for Sqlx"))?, Self::Refund => Ok("refund".to_string()), + Self::RefundSessionized => Err(error_stack::report!(ParsingError::UnknownError) + .attach_printable("RefundSessionized table is not implemented for Sqlx"))?, Self::SdkEvents => Err(error_stack::report!(ParsingError::UnknownError) .attach_printable("SdkEventsAudit table is not implemented for Sqlx"))?, Self::SdkEventsAnalytics => Err(error_stack::report!(ParsingError::UnknownError) @@ -725,6 +1007,10 @@ impl ToSql for AnalyticsCollection { .attach_printable("ApiEvents table is not implemented for Sqlx"))?, Self::FraudCheck => Ok("fraud_check".to_string()), Self::PaymentIntent => Ok("payment_intent".to_string()), + Self::PaymentIntentSessionized => Err(error_stack::report!( + ParsingError::UnknownError + ) + .attach_printable("PaymentIntentSessionized 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) @@ -734,6 +1020,8 @@ impl ToSql for AnalyticsCollection { Self::OutgoingWebhookEvent => Err(error_stack::report!(ParsingError::UnknownError) .attach_printable("OutgoingWebhookEvents table is not implemented for Sqlx"))?, Self::Dispute => Ok("dispute".to_string()), + Self::DisputeSessionized => Err(error_stack::report!(ParsingError::UnknownError) + .attach_printable("DisputeSessionized table is not implemented for Sqlx"))?, } } } diff --git a/crates/analytics/src/types.rs b/crates/analytics/src/types.rs index 6593e5d6eb04..86056338106b 100644 --- a/crates/analytics/src/types.rs +++ b/crates/analytics/src/types.rs @@ -26,15 +26,19 @@ pub enum AnalyticsDomain { #[derive(Debug, strum::AsRefStr, strum::Display, Clone, Copy)] pub enum AnalyticsCollection { Payment, + PaymentSessionized, Refund, + RefundSessionized, FraudCheck, SdkEvents, SdkEventsAnalytics, ApiEvents, PaymentIntent, + PaymentIntentSessionized, ConnectorEvents, OutgoingWebhookEvent, Dispute, + DisputeSessionized, ApiEventsAnalytics, ActivePaymentsAnalytics, } @@ -56,6 +60,12 @@ impl AsRef for DBEnumWrapper { } } +impl Default for DBEnumWrapper { + fn default() -> Self { + Self(T::default()) + } +} + impl FromStr for DBEnumWrapper where T: FromStr + Display, diff --git a/crates/analytics/src/utils.rs b/crates/analytics/src/utils.rs index 7b73f5a1c1df..fc21bf098192 100644 --- a/crates/analytics/src/utils.rs +++ b/crates/analytics/src/utils.rs @@ -12,11 +12,39 @@ use api_models::analytics::{ use strum::IntoEnumIterator; pub fn get_payment_dimensions() -> Vec { - PaymentDimensions::iter().map(Into::into).collect() + vec![ + PaymentDimensions::Connector, + PaymentDimensions::PaymentMethod, + PaymentDimensions::PaymentMethodType, + PaymentDimensions::Currency, + PaymentDimensions::AuthType, + PaymentDimensions::PaymentStatus, + PaymentDimensions::ClientSource, + PaymentDimensions::ClientVersion, + PaymentDimensions::ProfileId, + PaymentDimensions::CardNetwork, + PaymentDimensions::MerchantId, + ] + .into_iter() + .map(Into::into) + .collect() } pub fn get_payment_intent_dimensions() -> Vec { - PaymentIntentDimensions::iter().map(Into::into).collect() + vec![ + PaymentIntentDimensions::PaymentIntentStatus, + PaymentIntentDimensions::Currency, + PaymentIntentDimensions::ProfileId, + PaymentIntentDimensions::Connector, + PaymentIntentDimensions::AuthType, + PaymentIntentDimensions::PaymentMethod, + PaymentIntentDimensions::PaymentMethodType, + PaymentIntentDimensions::CardNetwork, + PaymentIntentDimensions::MerchantId, + ] + .into_iter() + .map(Into::into) + .collect() } pub fn get_refund_dimensions() -> Vec { diff --git a/crates/api_models/Cargo.toml b/crates/api_models/Cargo.toml index e8668aab5024..c159af972498 100644 --- a/crates/api_models/Cargo.toml +++ b/crates/api_models/Cargo.toml @@ -20,8 +20,11 @@ v1 = ["common_utils/v1"] v2 = ["common_utils/v2", "customer_v2"] customer_v2 = ["common_utils/customer_v2"] payment_methods_v2 = ["common_utils/payment_methods_v2"] +dynamic_routing = [] +control_center_theme = ["dep:actix-web", "dep:actix-multipart"] [dependencies] +actix-multipart = { version = "0.6.1", optional = true } actix-web = { version = "4.5.1", optional = true } error-stack = "0.4.1" indexmap = "2.3.0" @@ -39,6 +42,7 @@ nutype = { version = "0.4.2", features = ["serde"] } # First party crates cards = { version = "0.1.0", path = "../cards" } common_enums = { version = "0.1.0", path = "../common_enums" } +common_types = { version = "0.1.0", path = "../common_types" } common_utils = { version = "0.1.0", path = "../common_utils" } euclid = { version = "0.1.0", path = "../euclid" } masking = { version = "0.1.0", path = "../masking", default-features = false, features = ["alloc", "serde"] } diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index 6e7f4f29d036..ef25a4350d95 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -11,7 +11,7 @@ use common_utils::{ use common_utils::{crypto::OptionalEncryptableName, ext_traits::ValueExt}; #[cfg(feature = "v2")] use masking::ExposeInterface; -use masking::Secret; +use masking::{PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use url; use utoipa::ToSchema; @@ -178,7 +178,8 @@ impl MerchantAccountCreate { #[cfg(feature = "v2")] #[derive(Clone, Debug, Deserialize, ToSchema, Serialize)] #[serde(deny_unknown_fields)] -pub struct MerchantAccountCreate { +#[schema(as = MerchantAccountCreate)] +pub struct MerchantAccountCreateWithoutOrgId { /// Name of the Merchant Account, This will be used as a prefix to generate the id #[schema(value_type= String, max_length = 64, example = "NewAge Retailer")] pub merchant_name: Secret, @@ -189,9 +190,17 @@ pub struct MerchantAccountCreate { /// Metadata is useful for storing additional, unstructured information about the merchant account. #[schema(value_type = Option, example = r#"{ "city": "NY", "unit": "245" }"#)] pub metadata: Option, +} - /// The id of the organization to which the merchant belongs to. Please use the organization endpoint to create an organization - #[schema(value_type = String, max_length = 64, min_length = 1, example = "org_q98uSGAYbjEwqs0mJwnz")] +// In v2 the struct used in the API is MerchantAccountCreateWithoutOrgId +// The following struct is only used internally, so we can reuse the common +// part of `create_merchant_account` without duplicating its code for v2 +#[cfg(feature = "v2")] +#[derive(Clone, Debug, Serialize)] +pub struct MerchantAccountCreate { + pub merchant_name: Secret, + pub merchant_details: Option, + pub metadata: Option, pub organization_id: id_type::OrganizationId, } @@ -667,9 +676,11 @@ pub struct MerchantConnectorCreate { /// Type of the Connector for the financial use case. Could range from Payments to Accounting to Banking. #[schema(value_type = ConnectorType, example = "payment_processor")] pub connector_type: api_enums::ConnectorType, + /// Name of the Connector #[schema(value_type = Connector, example = "stripe")] pub connector_name: api_enums::Connector, + /// This is an unique label you can generate and pass in order to identify this connector account on your Hyperswitch dashboard and reports, If not passed then if will take `connector_name`_`profile_name`. Eg: if your profile label is `default`, connector label can be `stripe_default` #[schema(example = "stripe_US_travel")] pub connector_label: Option, @@ -683,36 +694,8 @@ pub struct MerchantConnectorCreate { pub connector_account_details: Option, /// An object containing the details about the payment methods that need to be enabled under this merchant connector account - #[schema(example = json!([ - { - "payment_method": "wallet", - "payment_method_types": [ - "upi_collect", - "upi_intent" - ], - "payment_method_issuers": [ - "labore magna ipsum", - "aute" - ], - "payment_schemes": [ - "Discover", - "Discover" - ], - "accepted_currencies": { - "type": "enable_only", - "list": ["USD", "EUR"] - }, - "accepted_countries": { - "type": "disable_only", - "list": ["FR", "DE","IN"] - }, - "minimum_amount": 1, - "maximum_amount": 68607706, - "recurring_enabled": true, - "installment_payment_enabled": true - } - ]))] - pub payment_methods_enabled: Option>, + #[schema(value_type = PaymentMethodsEnabled)] + pub payment_methods_enabled: Option>, /// Webhook details of this merchant connector #[schema(example = json!({ @@ -1047,36 +1030,8 @@ pub struct MerchantConnectorResponse { pub connector_account_details: pii::SecretSerdeValue, /// An object containing the details about the payment methods that need to be enabled under this merchant connector account - #[schema(example = json!([ - { - "payment_method": "wallet", - "payment_method_types": [ - "upi_collect", - "upi_intent" - ], - "payment_method_issuers": [ - "labore magna ipsum", - "aute" - ], - "payment_schemes": [ - "Discover", - "Discover" - ], - "accepted_currencies": { - "type": "enable_only", - "list": ["USD", "EUR"] - }, - "accepted_countries": { - "type": "disable_only", - "list": ["FR", "DE","IN"] - }, - "minimum_amount": 1, - "maximum_amount": 68607706, - "recurring_enabled": true, - "installment_payment_enabled": true - } - ]))] - pub payment_methods_enabled: Option>, + #[schema(value_type = Vec)] + pub payment_methods_enabled: Option>, /// Webhook details of this merchant connector #[schema(example = json!({ @@ -1304,10 +1259,6 @@ pub struct MerchantConnectorListResponse { ]))] pub payment_methods_enabled: Option>, - /// Metadata is useful for storing additional, unstructured information on an object. - #[schema(value_type = Option,max_length = 255,example = json!({ "city": "NY", "unit": "245" }))] - pub metadata: Option, - /// A boolean value to indicate if the connector is in Test mode. By default, its value is false. #[schema(default = false, example = false)] pub test_mode: Option, @@ -1340,13 +1291,6 @@ pub struct MerchantConnectorListResponse { #[schema(value_type = ConnectorStatus, example = "inactive")] pub status: api_enums::ConnectorStatus, - - #[schema(value_type = Option)] - pub additional_merchant_data: Option, - - /// The connector_wallets_details is used to store wallet details such as certificates and wallet credentials - #[schema(value_type = Option)] - pub connector_wallets_details: Option, } #[cfg(feature = "v1")] @@ -1383,40 +1327,8 @@ pub struct MerchantConnectorListResponse { pub profile_id: id_type::ProfileId, /// An object containing the details about the payment methods that need to be enabled under this merchant connector account - #[schema(example = json!([ - { - "payment_method": "wallet", - "payment_method_types": [ - "upi_collect", - "upi_intent" - ], - "payment_method_issuers": [ - "labore magna ipsum", - "aute" - ], - "payment_schemes": [ - "Discover", - "Discover" - ], - "accepted_currencies": { - "type": "enable_only", - "list": ["USD", "EUR"] - }, - "accepted_countries": { - "type": "disable_only", - "list": ["FR", "DE","IN"] - }, - "minimum_amount": 1, - "maximum_amount": 68607706, - "recurring_enabled": true, - "installment_payment_enabled": true - } - ]))] - pub payment_methods_enabled: Option>, - - /// Metadata is useful for storing additional, unstructured information on an object. - #[schema(value_type = Option,max_length = 255,example = json!({ "city": "NY", "unit": "245" }))] - pub metadata: Option, + #[schema(value_type = Vec)] + pub payment_methods_enabled: Option>, /// A boolean value to indicate if the connector is disabled. By default, its value is false. #[schema(default = false, example = false)] @@ -1434,13 +1346,6 @@ pub struct MerchantConnectorListResponse { #[schema(value_type = ConnectorStatus, example = "inactive")] pub status: api_enums::ConnectorStatus, - - #[schema(value_type = Option)] - pub additional_merchant_data: Option, - - /// The connector_wallets_details is used to store wallet details such as certificates and wallet credentials - #[schema(value_type = Option)] - pub connector_wallets_details: Option, } #[cfg(feature = "v2")] @@ -1544,7 +1449,6 @@ pub struct MerchantConnectorUpdate { #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] #[serde(deny_unknown_fields)] - pub struct ConnectorWalletDetails { /// This field contains the Apple Pay certificates and credentials for iOS and Web Apple Pay flow #[serde(skip_serializing_if = "Option::is_none")] @@ -1558,6 +1462,10 @@ pub struct ConnectorWalletDetails { #[serde(skip_serializing_if = "Option::is_none")] #[schema(value_type = Option)] pub samsung_pay: Option, + /// This field contains the Paze certificates and credentials + #[serde(skip_serializing_if = "Option::is_none")] + #[schema(value_type = Option)] + pub paze: Option, } /// Create a new Merchant Connector for the merchant account. The connector could be a payment processor / facilitator / acquirer or specialized services like Fraud / Accounting etc." @@ -1578,36 +1486,8 @@ pub struct MerchantConnectorUpdate { pub connector_account_details: Option, /// An object containing the details about the payment methods that need to be enabled under this merchant connector account - #[schema(example = json!([ - { - "payment_method": "wallet", - "payment_method_types": [ - "upi_collect", - "upi_intent" - ], - "payment_method_issuers": [ - "labore magna ipsum", - "aute" - ], - "payment_schemes": [ - "Discover", - "Discover" - ], - "accepted_currencies": { - "type": "enable_only", - "list": ["USD", "EUR"] - }, - "accepted_countries": { - "type": "disable_only", - "list": ["FR", "DE","IN"] - }, - "minimum_amount": 1, - "maximum_amount": 68607706, - "recurring_enabled": true, - "installment_payment_enabled": true - } - ]))] - pub payment_methods_enabled: Option>, + #[schema(value_type = Option>)] + pub payment_methods_enabled: Option>, /// Webhook details of this merchant connector #[schema(example = json!({ @@ -1967,8 +1847,7 @@ pub struct ProfileCreate { #[serde(default)] pub is_tax_connector_enabled: bool, - /// Indicates if is_network_tokenization_enabled is enabled or not. - /// If set to `true` is_network_tokenization_enabled will be checked. + /// Indicates if network tokenization is enabled or not. #[serde(default)] pub is_network_tokenization_enabled: bool, @@ -1977,6 +1856,14 @@ pub struct ProfileCreate { /// Maximum number of auto retries allowed for a payment pub max_auto_retries_enabled: Option, + + /// Indicates if click to pay is enabled or not. + #[serde(default)] + pub is_click_to_pay_enabled: bool, + + /// Product authentication ids + #[schema(value_type = Option, example = r#"{ "key1": "value-1", "key2": "value-2" }"#)] + pub authentication_product_ids: Option>, } #[nutype::nutype( @@ -1995,7 +1882,7 @@ pub struct ProfileCreate { /// The URL to redirect after the completion of the operation #[schema(value_type = Option, max_length = 255, example = "https://www.example.com/success")] - pub return_url: Option, + pub return_url: Option, /// A boolean value to indicate if payment response hash needs to be enabled #[schema(default = true, example = true)] @@ -2082,10 +1969,18 @@ pub struct ProfileCreate { #[serde(default)] pub is_tax_connector_enabled: bool, - /// Indicates if is_network_tokenization_enabled is enabled or not. - /// If set to `true` is_network_tokenization_enabled will be checked. + /// Indicates if network tokenization is enabled or not. #[serde(default)] pub is_network_tokenization_enabled: bool, + + /// Indicates if click to pay is enabled or not. + #[schema(default = false, example = false)] + #[serde(default)] + pub is_click_to_pay_enabled: bool, + + /// Product authentication ids + #[schema(value_type = Option, example = r#"{ "key1": "value-1", "key2": "value-2" }"#)] + pub authentication_product_ids: Option>, } #[cfg(feature = "v1")] @@ -2194,7 +2089,7 @@ pub struct ProfileResponse { /// These key-value pairs are sent as additional custom headers in the outgoing webhook request. #[schema(value_type = Option, example = r#"{ "key1": "value-1", "key2": "value-2" }"#)] - pub outgoing_webhook_custom_http_headers: Option>>, + pub outgoing_webhook_custom_http_headers: Option, /// Merchant Connector id to be stored for tax_calculator connector #[schema(value_type = Option)] @@ -2204,8 +2099,7 @@ pub struct ProfileResponse { /// If set to `true` tax_connector_id will be checked. pub is_tax_connector_enabled: bool, - /// Indicates if is_network_tokenization_enabled is enabled or not. - /// If set to `true` is_network_tokenization_enabled will be checked. + /// Indicates if network tokenization is enabled or not. #[schema(default = false, example = false)] pub is_network_tokenization_enabled: bool, @@ -2215,6 +2109,14 @@ pub struct ProfileResponse { /// Maximum number of auto retries allowed for a payment pub max_auto_retries_enabled: Option, + + /// Indicates if click to pay is enabled or not. + #[schema(default = false, example = false)] + pub is_click_to_pay_enabled: bool, + + /// Product authentication ids + #[schema(value_type = Option, example = r#"{ "key1": "value-1", "key2": "value-2" }"#)] + pub authentication_product_ids: Option, } #[cfg(feature = "v2")] @@ -2234,7 +2136,7 @@ pub struct ProfileResponse { /// The URL to redirect after the completion of the operation #[schema(value_type = Option, max_length = 255, example = "https://www.example.com/success")] - pub return_url: Option, + pub return_url: Option, /// A boolean value to indicate if payment response hash needs to be enabled #[schema(default = true, example = true)] @@ -2306,7 +2208,7 @@ pub struct ProfileResponse { /// These key-value pairs are sent as additional custom headers in the outgoing webhook request. #[schema(value_type = Option, example = r#"{ "key1": "value-1", "key2": "value-2" }"#)] - pub outgoing_webhook_custom_http_headers: Option>>, + pub outgoing_webhook_custom_http_headers: Option, /// Will be used to determine the time till which your payment will be active once the payment session starts #[schema(value_type = Option, example = 900)] @@ -2324,10 +2226,20 @@ pub struct ProfileResponse { /// If set to `true` tax_connector_id will be checked. pub is_tax_connector_enabled: bool, - /// Indicates if is_network_tokenization_enabled is enabled or not. - /// If set to `true` is_network_tokenization_enabled will be checked. + /// Indicates if network tokenization is enabled or not. #[schema(default = false, example = false)] pub is_network_tokenization_enabled: bool, + + /// Indicates if CVV should be collected during payment or not. + pub should_collect_cvv_during_payment: bool, + + /// Indicates if click to pay is enabled or not. + #[schema(default = false, example = false)] + pub is_click_to_pay_enabled: bool, + + /// Product authentication ids + #[schema(value_type = Option, example = r#"{ "key1": "value-1", "key2": "value-2" }"#)] + pub authentication_product_ids: Option, } #[cfg(feature = "v1")] @@ -2442,7 +2354,7 @@ pub struct ProfileUpdate { #[serde(default)] pub dynamic_routing_algorithm: Option, - /// Indicates if is_network_tokenization_enabled is enabled or not. + /// Indicates if network tokenization is enabled or not. pub is_network_tokenization_enabled: Option, /// Indicates if is_auto_retries_enabled is enabled or not. @@ -2450,6 +2362,14 @@ pub struct ProfileUpdate { /// Maximum number of auto retries allowed for a payment pub max_auto_retries_enabled: Option, + + /// Indicates if click to pay is enabled or not. + #[schema(default = false, example = false)] + pub is_click_to_pay_enabled: Option, + + /// Product authentication ids + #[schema(value_type = Option, example = r#"{ "key1": "value-1", "key2": "value-2" }"#)] + pub authentication_product_ids: Option>, } #[cfg(feature = "v2")] @@ -2462,7 +2382,7 @@ pub struct ProfileUpdate { /// The URL to redirect after the completion of the operation #[schema(value_type = Option, max_length = 255, example = "https://www.example.com/success")] - pub return_url: Option, + pub return_url: Option, /// A boolean value to indicate if payment response hash needs to be enabled #[schema(default = true, example = true)] @@ -2551,8 +2471,16 @@ pub struct ProfileUpdate { /// If set to `true` tax_connector_id will be checked. pub is_tax_connector_enabled: Option, - /// Indicates if is_network_tokenization_enabled is enabled or not. + /// Indicates if network tokenization is enabled or not. pub is_network_tokenization_enabled: Option, + + /// Indicates if click to pay is enabled or not. + #[schema(default = false, example = false)] + pub is_click_to_pay_enabled: Option, + + /// Product authentication ids + #[schema(value_type = Option, example = r#"{ "key1": "value-1", "key2": "value-2" }"#)] + pub authentication_product_ids: Option>, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] @@ -2579,6 +2507,43 @@ pub struct BusinessPayoutLinkConfig { pub payout_test_mode: Option, } +#[derive(Clone, Debug, serde::Serialize)] +pub struct MaskedHeaders(HashMap); + +impl MaskedHeaders { + fn mask_value(value: &str) -> String { + let value_len = value.len(); + + let masked_value = if value_len <= 4 { + "*".repeat(value_len) + } else { + value + .char_indices() + .map(|(index, ch)| { + if index < 2 || index >= value_len - 2 { + // Show the first two and last two characters, mask the rest with '*' + ch + } else { + // Mask the remaining characters + '*' + } + }) + .collect::() + }; + + masked_value + } + + pub fn from_headers(headers: HashMap>) -> Self { + let masked_headers = headers + .into_iter() + .map(|(key, value)| (key, Self::mask_value(value.peek()))) + .collect(); + + Self(masked_headers) + } +} + #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] pub struct BusinessGenericLinkConfig { /// Custom domain name to be used for hosting the link @@ -2630,6 +2595,8 @@ pub struct BusinessPaymentLinkConfig { /// A list of allowed domains (glob patterns) where this link can be embedded / opened from #[schema(value_type = Option>)] pub allowed_domains: Option>, + /// Toggle for HyperSwitch branding visibility + pub branding_visibility: Option, } impl BusinessPaymentLinkConfig { @@ -2680,8 +2647,21 @@ pub struct PaymentLinkConfigRequest { /// Enable saved payment method option for payment link #[schema(default = false, example = true)] pub enabled_saved_payment_method: Option, + /// Hide card nickname field option for payment link + #[schema(default = false, example = true)] + pub hide_card_nickname_field: Option, + /// Show card form by default for payment link + #[schema(default = true, example = true)] + pub show_card_form_by_default: Option, /// Dynamic details related to merchant to be rendered in payment link pub transaction_details: Option>, + /// Configurations for the background image for details section + pub background_image: Option, + /// Custom layout for details section + #[schema(value_type = Option, example = "layout1")] + pub details_layout: Option, + /// Text for payment link's handle confirm button + pub payment_button_text: Option, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)] @@ -2709,6 +2689,19 @@ pub struct TransactionDetailsUiConfiguration { pub is_value_bold: Option, } +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)] +pub struct PaymentLinkBackgroundImageConfig { + /// URL of the image + #[schema(value_type = String, example = "https://hyperswitch.io/favicon.ico")] + pub url: common_utils::types::Url, + /// Position of the image in the UI + #[schema(value_type = Option, example = "top-left")] + pub position: Option, + /// Size of the image in the UI + #[schema(value_type = Option, example = "contain")] + pub size: Option, +} + #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, ToSchema)] pub struct PaymentLinkConfig { /// custom theme for the payment link @@ -2723,10 +2716,23 @@ pub struct PaymentLinkConfig { pub display_sdk_only: bool, /// Enable saved payment method option for payment link pub enabled_saved_payment_method: bool, + /// Hide card nickname field option for payment link + pub hide_card_nickname_field: bool, + /// Show card form by default for payment link + pub show_card_form_by_default: bool, /// A list of allowed domains (glob patterns) where this link can be embedded / opened from pub allowed_domains: Option>, /// Dynamic details related to merchant to be rendered in payment link pub transaction_details: Option>, + /// Configurations for the background image for details section + pub background_image: Option, + /// Custom layout for details section + #[schema(value_type = Option, example = "layout1")] + pub details_layout: Option, + /// Toggle for HyperSwitch branding visibility + pub branding_visibility: Option, + /// Text for payment link's handle confirm button + pub payment_button_text: Option, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)] diff --git a/crates/api_models/src/analytics.rs b/crates/api_models/src/analytics.rs index 0379ec09547f..132272f0e496 100644 --- a/crates/api_models/src/analytics.rs +++ b/crates/api_models/src/analytics.rs @@ -12,7 +12,7 @@ use self::{ frm::{FrmDimensions, FrmMetrics}, payment_intents::{PaymentIntentDimensions, PaymentIntentMetrics}, payments::{PaymentDimensions, PaymentDistributions, PaymentMetrics}, - refunds::{RefundDimensions, RefundMetrics}, + refunds::{RefundDimensions, RefundDistributions, RefundMetrics}, sdk_events::{SdkEventDimensions, SdkEventMetrics}, }; pub mod active_payments; @@ -62,7 +62,42 @@ pub enum Granularity { #[serde(rename = "G_ONEDAY")] OneDay, } - +pub trait ForexMetric { + fn is_forex_metric(&self) -> bool; +} + +#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AnalyticsRequest { + pub payment_intent: Option, + pub payment_attempt: Option, + pub refund: Option, + pub dispute: Option, +} + +impl AnalyticsRequest { + pub fn requires_forex_functionality(&self) -> bool { + self.payment_attempt + .as_ref() + .map(|req| req.metrics.iter().any(|metric| metric.is_forex_metric())) + .unwrap_or_default() + || self + .payment_intent + .as_ref() + .map(|req| req.metrics.iter().any(|metric| metric.is_forex_metric())) + .unwrap_or_default() + || self + .refund + .as_ref() + .map(|req| req.metrics.iter().any(|metric| metric.is_forex_metric())) + .unwrap_or_default() + || self + .dispute + .as_ref() + .map(|req| req.metrics.iter().any(|metric| metric.is_forex_metric())) + .unwrap_or_default() + } +} #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct GetPaymentMetricRequest { @@ -73,7 +108,7 @@ pub struct GetPaymentMetricRequest { #[serde(default)] pub filters: payments::PaymentFilters, pub metrics: HashSet, - pub distribution: Option, + pub distribution: Option, #[serde(default)] pub delta: bool, } @@ -98,15 +133,23 @@ impl Into for QueryLimit { #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] -pub struct Distribution { +pub struct PaymentDistributionBody { pub distribution_for: PaymentDistributions, pub distribution_cardinality: QueryLimit, } +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RefundDistributionBody { + pub distribution_for: RefundDistributions, + pub distribution_cardinality: QueryLimit, +} + #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct ReportRequest { pub time_range: TimeRange, + pub emails: Option>>, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] @@ -142,6 +185,7 @@ pub struct GetRefundMetricRequest { #[serde(default)] pub filters: refunds::RefundFilters, pub metrics: HashSet, + pub distribution: Option, #[serde(default)] pub delta: bool, } @@ -200,6 +244,43 @@ pub struct AnalyticsMetadata { pub current_time_range: TimeRange, } +#[derive(Debug, serde::Serialize)] +pub struct PaymentsAnalyticsMetadata { + pub total_payment_processed_amount: Option, + pub total_payment_processed_amount_in_usd: Option, + pub total_payment_processed_amount_without_smart_retries: Option, + pub total_payment_processed_amount_without_smart_retries_usd: Option, + pub total_payment_processed_count: Option, + pub total_payment_processed_count_without_smart_retries: Option, + pub total_failure_reasons_count: Option, + pub total_failure_reasons_count_without_smart_retries: Option, +} + +#[derive(Debug, serde::Serialize)] +pub struct PaymentIntentsAnalyticsMetadata { + pub total_success_rate: Option, + pub total_success_rate_without_smart_retries: Option, + pub total_smart_retried_amount: Option, + pub total_smart_retried_amount_without_smart_retries: Option, + pub total_payment_processed_amount: Option, + pub total_payment_processed_amount_without_smart_retries: Option, + pub total_smart_retried_amount_in_usd: Option, + pub total_smart_retried_amount_without_smart_retries_in_usd: Option, + pub total_payment_processed_amount_in_usd: Option, + pub total_payment_processed_amount_without_smart_retries_in_usd: Option, + pub total_payment_processed_count: Option, + pub total_payment_processed_count_without_smart_retries: Option, +} + +#[derive(Debug, serde::Serialize)] +pub struct RefundsAnalyticsMetadata { + pub total_refund_success_rate: Option, + pub total_refund_processed_amount: Option, + pub total_refund_processed_amount_in_usd: Option, + pub total_refund_processed_count: Option, + pub total_refund_reason_count: Option, + pub total_refund_error_message_count: Option, +} #[derive(Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct GetPaymentFiltersRequest { @@ -244,7 +325,6 @@ pub struct PaymentIntentFilterValue { #[derive(Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] - pub struct GetRefundFilterRequest { pub time_range: TimeRange, #[serde(default)] @@ -259,7 +339,6 @@ pub struct RefundFiltersResponse { #[derive(Debug, serde::Serialize, Eq, PartialEq)] #[serde(rename_all = "camelCase")] - pub struct RefundFilterValue { pub dimension: RefundDimensions, pub values: Vec, @@ -313,6 +392,11 @@ pub struct SdkEventFilterValue { pub values: Vec, } +#[derive(Debug, serde::Serialize)] +pub struct DisputesAnalyticsMetadata { + pub total_disputed_amount: Option, + pub total_dispute_lost_amount: Option, +} #[derive(Debug, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct MetricsResponse { @@ -320,6 +404,32 @@ pub struct MetricsResponse { pub meta_data: [AnalyticsMetadata; 1], } +#[derive(Debug, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PaymentsMetricsResponse { + pub query_data: Vec, + pub meta_data: [PaymentsAnalyticsMetadata; 1], +} + +#[derive(Debug, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PaymentIntentsMetricsResponse { + pub query_data: Vec, + pub meta_data: [PaymentIntentsAnalyticsMetadata; 1], +} + +#[derive(Debug, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RefundsMetricsResponse { + pub query_data: Vec, + pub meta_data: [RefundsAnalyticsMetadata; 1], +} +#[derive(Debug, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DisputesMetricsResponse { + pub query_data: Vec, + pub meta_data: [DisputesAnalyticsMetadata; 1], +} #[derive(Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct GetApiEventFiltersRequest { @@ -371,7 +481,6 @@ pub struct DisputeFiltersResponse { #[derive(Debug, serde::Serialize, Eq, PartialEq)] #[serde(rename_all = "camelCase")] - pub struct DisputeFilterValue { pub dimension: DisputeDimensions, pub values: Vec, @@ -390,3 +499,13 @@ pub struct GetDisputeMetricRequest { #[serde(default)] pub delta: bool, } + +#[derive(Clone, Debug, Default, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub struct SankeyResponse { + pub count: i64, + pub status: String, + pub refunds_status: Option, + pub dispute_status: Option, + pub first_attempt: i64, +} diff --git a/crates/api_models/src/analytics/disputes.rs b/crates/api_models/src/analytics/disputes.rs index edb85c129e67..e373704b87c5 100644 --- a/crates/api_models/src/analytics/disputes.rs +++ b/crates/api_models/src/analytics/disputes.rs @@ -3,7 +3,7 @@ use std::{ hash::{Hash, Hasher}, }; -use super::{NameDescription, TimeRange}; +use super::{ForexMetric, NameDescription, TimeRange}; use crate::enums::DisputeStage; #[derive( @@ -24,6 +24,17 @@ pub enum DisputeMetrics { DisputeStatusMetric, TotalAmountDisputed, TotalDisputeLostAmount, + SessionizedDisputeStatusMetric, + SessionizedTotalAmountDisputed, + SessionizedTotalDisputeLostAmount, +} +impl ForexMetric for DisputeMetrics { + fn is_forex_metric(&self) -> bool { + matches!( + self, + Self::TotalAmountDisputed | Self::TotalDisputeLostAmount + ) + } } #[derive( @@ -122,8 +133,8 @@ pub struct DisputeMetricsBucketValue { pub disputes_challenged: Option, pub disputes_won: Option, pub disputes_lost: Option, - pub total_amount_disputed: Option, - pub total_dispute_lost_amount: Option, + pub disputed_amount: Option, + pub dispute_lost_amount: Option, pub total_dispute: Option, } #[derive(Debug, serde::Serialize)] diff --git a/crates/api_models/src/analytics/payment_intents.rs b/crates/api_models/src/analytics/payment_intents.rs index 61e336185b67..15c107dfb66c 100644 --- a/crates/api_models/src/analytics/payment_intents.rs +++ b/crates/api_models/src/analytics/payment_intents.rs @@ -5,8 +5,10 @@ use std::{ use common_utils::id_type; -use super::{NameDescription, TimeRange}; -use crate::enums::{Currency, IntentStatus}; +use super::{ForexMetric, NameDescription, TimeRange}; +use crate::enums::{ + AuthenticationType, Connector, Currency, IntentStatus, PaymentMethod, PaymentMethodType, +}; #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)] pub struct PaymentIntentFilters { @@ -16,6 +18,26 @@ pub struct PaymentIntentFilters { pub currency: Vec, #[serde(default)] pub profile_id: Vec, + #[serde(default)] + pub connector: Vec, + #[serde(default)] + pub auth_type: Vec, + #[serde(default)] + pub payment_method: Vec, + #[serde(default)] + pub payment_method_type: Vec, + #[serde(default)] + pub card_network: Vec, + #[serde(default)] + pub merchant_id: Vec, + #[serde(default)] + pub card_last_4: Vec, + #[serde(default)] + pub card_issuer: Vec, + #[serde(default)] + pub error_reason: Vec, + #[serde(default)] + pub customer_id: Vec, } #[derive( @@ -40,6 +62,19 @@ pub enum PaymentIntentDimensions { PaymentIntentStatus, Currency, ProfileId, + Connector, + #[strum(serialize = "authentication_type")] + #[serde(rename = "authentication_type")] + AuthType, + PaymentMethod, + PaymentMethodType, + CardNetwork, + MerchantId, + #[strum(serialize = "card_last_4")] + #[serde(rename = "card_last_4")] + CardLast4, + CardIssuer, + ErrorReason, } #[derive( @@ -61,6 +96,26 @@ pub enum PaymentIntentMetrics { TotalSmartRetries, SmartRetriedAmount, PaymentIntentCount, + PaymentsSuccessRate, + PaymentProcessedAmount, + SessionizedSuccessfulSmartRetries, + SessionizedTotalSmartRetries, + SessionizedSmartRetriedAmount, + SessionizedPaymentIntentCount, + SessionizedPaymentsSuccessRate, + SessionizedPaymentProcessedAmount, + SessionizedPaymentsDistribution, +} +impl ForexMetric for PaymentIntentMetrics { + fn is_forex_metric(&self) -> bool { + matches!( + self, + Self::PaymentProcessedAmount + | Self::SmartRetriedAmount + | Self::SessionizedPaymentProcessedAmount + | Self::SessionizedSmartRetriedAmount + ) + } } #[derive(Debug, Default, serde::Serialize)] @@ -75,6 +130,7 @@ pub mod metric_behaviour { pub struct TotalSmartRetries; pub struct SmartRetriedAmount; pub struct PaymentIntentCount; + pub struct PaymentsSuccessRate; } impl From for NameDescription { @@ -100,6 +156,15 @@ pub struct PaymentIntentMetricsBucketIdentifier { pub status: Option, pub currency: Option, pub profile_id: Option, + pub connector: Option, + pub auth_type: Option, + pub payment_method: Option, + pub payment_method_type: Option, + pub card_network: Option, + pub merchant_id: Option, + pub card_last_4: Option, + pub card_issuer: Option, + pub error_reason: Option, #[serde(rename = "time_range")] pub time_bucket: TimeRange, #[serde(rename = "time_bucket")] @@ -113,12 +178,30 @@ impl PaymentIntentMetricsBucketIdentifier { status: Option, currency: Option, profile_id: Option, + connector: Option, + auth_type: Option, + payment_method: Option, + payment_method_type: Option, + card_network: Option, + merchant_id: Option, + card_last_4: Option, + card_issuer: Option, + error_reason: Option, normalized_time_range: TimeRange, ) -> Self { Self { status, currency, profile_id, + connector, + auth_type, + payment_method, + payment_method_type, + card_network, + merchant_id, + card_last_4, + card_issuer, + error_reason, time_bucket: normalized_time_range, start_time: normalized_time_range.start_time, } @@ -130,6 +213,15 @@ impl Hash for PaymentIntentMetricsBucketIdentifier { self.status.map(|i| i.to_string()).hash(state); self.currency.hash(state); self.profile_id.hash(state); + self.connector.hash(state); + self.auth_type.map(|i| i.to_string()).hash(state); + self.payment_method.hash(state); + self.payment_method_type.hash(state); + self.card_network.hash(state); + self.merchant_id.hash(state); + self.card_last_4.hash(state); + self.card_issuer.hash(state); + self.error_reason.hash(state); self.time_bucket.hash(state); } } @@ -149,7 +241,23 @@ pub struct PaymentIntentMetricsBucketValue { pub successful_smart_retries: Option, pub total_smart_retries: Option, pub smart_retried_amount: Option, + pub smart_retried_amount_in_usd: Option, + pub smart_retried_amount_without_smart_retries: Option, + pub smart_retried_amount_without_smart_retries_in_usd: Option, pub payment_intent_count: Option, + pub successful_payments: Option, + pub successful_payments_without_smart_retries: Option, + pub total_payments: Option, + pub payments_success_rate: Option, + pub payments_success_rate_without_smart_retries: Option, + pub payment_processed_amount: Option, + pub payment_processed_amount_in_usd: Option, + pub payment_processed_count: Option, + pub payment_processed_amount_without_smart_retries: Option, + pub payment_processed_amount_without_smart_retries_in_usd: Option, + pub payment_processed_count_without_smart_retries: Option, + pub payments_success_rate_distribution_without_smart_retries: Option, + pub payments_failure_rate_distribution_without_smart_retries: Option, } #[derive(Debug, serde::Serialize)] diff --git a/crates/api_models/src/analytics/payments.rs b/crates/api_models/src/analytics/payments.rs index c474d47ee632..691e827043f1 100644 --- a/crates/api_models/src/analytics/payments.rs +++ b/crates/api_models/src/analytics/payments.rs @@ -5,7 +5,7 @@ use std::{ use common_utils::id_type; -use super::{NameDescription, TimeRange}; +use super::{ForexMetric, NameDescription, TimeRange}; use crate::enums::{ AttemptStatus, AuthenticationType, CardNetwork, Connector, Currency, PaymentMethod, PaymentMethodType, @@ -33,6 +33,16 @@ pub struct PaymentFilters { pub card_network: Vec, #[serde(default)] pub profile_id: Vec, + #[serde(default)] + pub merchant_id: Vec, + #[serde(default)] + pub card_last_4: Vec, + #[serde(default)] + pub card_issuer: Vec, + #[serde(default)] + pub error_reason: Vec, + #[serde(default)] + pub first_attempt: Vec, } #[derive( @@ -68,6 +78,12 @@ pub enum PaymentDimensions { ClientVersion, ProfileId, CardNetwork, + MerchantId, + #[strum(serialize = "card_last_4")] + #[serde(rename = "card_last_4")] + CardLast4, + CardIssuer, + ErrorReason, } #[derive( @@ -92,8 +108,28 @@ pub enum PaymentMetrics { AvgTicketSize, RetriesCount, ConnectorSuccessRate, + SessionizedPaymentSuccessRate, + SessionizedPaymentCount, + SessionizedPaymentSuccessCount, + SessionizedPaymentProcessedAmount, + SessionizedAvgTicketSize, + SessionizedRetriesCount, + SessionizedConnectorSuccessRate, + PaymentsDistribution, + FailureReasons, } +impl ForexMetric for PaymentMetrics { + fn is_forex_metric(&self) -> bool { + matches!( + self, + Self::PaymentProcessedAmount + | Self::AvgTicketSize + | Self::SessionizedPaymentProcessedAmount + | Self::SessionizedAvgTicketSize + ) + } +} #[derive(Debug, Default, serde::Serialize)] pub struct ErrorResult { pub reason: String, @@ -159,6 +195,11 @@ pub struct PaymentMetricsBucketIdentifier { pub client_source: Option, pub client_version: Option, pub profile_id: Option, + pub card_network: Option, + pub merchant_id: Option, + pub card_last_4: Option, + pub card_issuer: Option, + pub error_reason: Option, #[serde(rename = "time_range")] pub time_bucket: TimeRange, // Coz FE sucks @@ -179,6 +220,11 @@ impl PaymentMetricsBucketIdentifier { client_source: Option, client_version: Option, profile_id: Option, + card_network: Option, + merchant_id: Option, + card_last_4: Option, + card_issuer: Option, + error_reason: Option, normalized_time_range: TimeRange, ) -> Self { Self { @@ -191,6 +237,11 @@ impl PaymentMetricsBucketIdentifier { client_source, client_version, profile_id, + card_network, + merchant_id, + card_last_4, + card_issuer, + error_reason, time_bucket: normalized_time_range, start_time: normalized_time_range.start_time, } @@ -208,6 +259,11 @@ impl Hash for PaymentMetricsBucketIdentifier { self.client_source.hash(state); self.client_version.hash(state); self.profile_id.hash(state); + self.card_network.hash(state); + self.merchant_id.hash(state); + self.card_last_4.hash(state); + self.card_issuer.hash(state); + self.error_reason.hash(state); self.time_bucket.hash(state); } } @@ -228,11 +284,24 @@ pub struct PaymentMetricsBucketValue { pub payment_count: Option, pub payment_success_count: Option, pub payment_processed_amount: Option, + pub payment_processed_amount_in_usd: Option, + pub payment_processed_count: Option, + pub payment_processed_amount_without_smart_retries: Option, + pub payment_processed_amount_without_smart_retries_usd: Option, + pub payment_processed_count_without_smart_retries: Option, pub avg_ticket_size: Option, pub payment_error_message: Option>, pub retries_count: Option, pub retries_amount_processed: Option, pub connector_success_rate: Option, + pub payments_success_rate_distribution: Option, + pub payments_success_rate_distribution_without_smart_retries: Option, + pub payments_success_rate_distribution_with_only_retries: Option, + pub payments_failure_rate_distribution: Option, + pub payments_failure_rate_distribution_without_smart_retries: Option, + pub payments_failure_rate_distribution_with_only_retries: Option, + pub failure_reason_count: Option, + pub failure_reason_count_without_smart_retries: Option, } #[derive(Debug, serde::Serialize)] diff --git a/crates/api_models/src/analytics/refunds.rs b/crates/api_models/src/analytics/refunds.rs index 8acb51e764c0..84954e70910f 100644 --- a/crates/api_models/src/analytics/refunds.rs +++ b/crates/api_models/src/analytics/refunds.rs @@ -5,7 +5,7 @@ use std::{ use common_utils::id_type; -use crate::{enums::Currency, refunds::RefundStatus}; +use crate::enums::{Currency, RefundStatus}; #[derive( Clone, @@ -30,7 +30,7 @@ pub enum RefundType { RetryRefund, } -use super::{NameDescription, TimeRange}; +use super::{ForexMetric, NameDescription, TimeRange}; #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)] pub struct RefundFilters { #[serde(default)] @@ -43,6 +43,10 @@ pub struct RefundFilters { pub refund_type: Vec, #[serde(default)] pub profile_id: Vec, + #[serde(default)] + pub refund_reason: Vec, + #[serde(default)] + pub refund_error_message: Vec, } #[derive( @@ -67,6 +71,8 @@ pub enum RefundDimensions { Connector, RefundType, ProfileId, + RefundReason, + RefundErrorMessage, } #[derive( @@ -88,6 +94,56 @@ pub enum RefundMetrics { RefundCount, RefundSuccessCount, RefundProcessedAmount, + SessionizedRefundSuccessRate, + SessionizedRefundCount, + SessionizedRefundSuccessCount, + SessionizedRefundProcessedAmount, + SessionizedRefundReason, + SessionizedRefundErrorMessage, +} + +#[derive(Debug, Default, serde::Serialize)] +pub struct ReasonsResult { + pub reason: String, + pub count: i64, + pub percentage: f64, +} + +#[derive(Debug, Default, serde::Serialize)] +pub struct ErrorMessagesResult { + pub error_message: String, + pub count: i64, + pub percentage: f64, +} + +#[derive( + Clone, + Copy, + Debug, + Hash, + PartialEq, + Eq, + serde::Serialize, + serde::Deserialize, + strum::Display, + strum::EnumIter, + strum::AsRefStr, +)] +#[strum(serialize_all = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum RefundDistributions { + #[strum(serialize = "refund_reason")] + SessionizedRefundReason, + #[strum(serialize = "refund_error_message")] + SessionizedRefundErrorMessage, +} +impl ForexMetric for RefundMetrics { + fn is_forex_metric(&self) -> bool { + matches!( + self, + Self::RefundProcessedAmount | Self::SessionizedRefundProcessedAmount + ) + } } pub mod metric_behaviour { @@ -120,9 +176,10 @@ pub struct RefundMetricsBucketIdentifier { pub currency: Option, pub refund_status: Option, pub connector: Option, - pub refund_type: Option, pub profile_id: Option, + pub refund_reason: Option, + pub refund_error_message: Option, #[serde(rename = "time_range")] pub time_bucket: TimeRange, #[serde(rename = "time_bucket")] @@ -137,6 +194,8 @@ impl Hash for RefundMetricsBucketIdentifier { self.connector.hash(state); self.refund_type.hash(state); self.profile_id.hash(state); + self.refund_reason.hash(state); + self.refund_error_message.hash(state); self.time_bucket.hash(state); } } @@ -151,12 +210,15 @@ impl PartialEq for RefundMetricsBucketIdentifier { } impl RefundMetricsBucketIdentifier { + #[allow(clippy::too_many_arguments)] pub fn new( currency: Option, refund_status: Option, connector: Option, refund_type: Option, profile_id: Option, + refund_reason: Option, + refund_error_message: Option, normalized_time_range: TimeRange, ) -> Self { Self { @@ -165,6 +227,8 @@ impl RefundMetricsBucketIdentifier { connector, refund_type, profile_id, + refund_reason, + refund_error_message, time_bucket: normalized_time_range, start_time: normalized_time_range.start_time, } @@ -172,10 +236,18 @@ impl RefundMetricsBucketIdentifier { } #[derive(Debug, serde::Serialize)] pub struct RefundMetricsBucketValue { + pub successful_refunds: Option, + pub total_refunds: Option, pub refund_success_rate: Option, pub refund_count: Option, pub refund_success_count: Option, pub refund_processed_amount: Option, + pub refund_processed_amount_in_usd: Option, + pub refund_processed_count: Option, + pub refund_reason_distribution: Option>, + pub refund_error_message_distribution: Option>, + pub refund_reason_count: Option, + pub refund_error_message_count: Option, } #[derive(Debug, serde::Serialize)] pub struct RefundMetricsBucketResponse { diff --git a/crates/api_models/src/analytics/search.rs b/crates/api_models/src/analytics/search.rs index 24dd0effcbbe..a33dd100c791 100644 --- a/crates/api_models/src/analytics/search.rs +++ b/crates/api_models/src/analytics/search.rs @@ -9,6 +9,11 @@ pub struct SearchFilters { pub status: Option>, pub customer_email: Option>>, pub search_tags: Option>>, + pub connector: Option>, + pub payment_method_type: Option>, + pub card_network: Option>, + pub card_last_4: Option>, + pub payment_id: Option>, } impl SearchFilters { pub fn is_all_none(&self) -> bool { @@ -17,6 +22,11 @@ impl SearchFilters { && self.status.is_none() && self.customer_email.is_none() && self.search_tags.is_none() + && self.connector.is_none() + && self.payment_method_type.is_none() + && self.card_network.is_none() + && self.card_last_4.is_none() + && self.payment_id.is_none() } } @@ -58,6 +68,10 @@ pub enum SearchIndex { PaymentIntents, Refunds, Disputes, + SessionizerPaymentAttempts, + SessionizerPaymentIntents, + SessionizerRefunds, + SessionizerDisputes, } #[derive(Debug, strum::EnumIter, Clone, serde::Deserialize, serde::Serialize, Copy)] diff --git a/crates/api_models/src/api_keys.rs b/crates/api_models/src/api_keys.rs index 65cc6b9a25a7..3c4d566f2354 100644 --- a/crates/api_models/src/api_keys.rs +++ b/crates/api_models/src/api_keys.rs @@ -29,8 +29,8 @@ pub struct CreateApiKeyRequest { #[derive(Debug, Serialize, ToSchema)] pub struct CreateApiKeyResponse { /// The identifier for the API Key. - #[schema(max_length = 64, example = "5hEEqkgJUyuxgSKGArHA4mWSnX")] - pub key_id: String, + #[schema(max_length = 64, example = "5hEEqkgJUyuxgSKGArHA4mWSnX", value_type = String)] + pub key_id: common_utils::id_type::ApiKeyId, /// The identifier for the Merchant Account. #[schema(max_length = 64, example = "y3oqhf46pyzuxjbcn2giaqnb44", value_type = String)] @@ -72,8 +72,8 @@ pub struct CreateApiKeyResponse { #[derive(Debug, Serialize, ToSchema)] pub struct RetrieveApiKeyResponse { /// The identifier for the API Key. - #[schema(max_length = 64, example = "5hEEqkgJUyuxgSKGArHA4mWSnX")] - pub key_id: String, + #[schema(max_length = 64, example = "5hEEqkgJUyuxgSKGArHA4mWSnX", value_type = String)] + pub key_id: common_utils::id_type::ApiKeyId, /// The identifier for the Merchant Account. #[schema(max_length = 64, example = "y3oqhf46pyzuxjbcn2giaqnb44", value_type = String)] @@ -131,7 +131,8 @@ pub struct UpdateApiKeyRequest { pub expiration: Option, #[serde(skip_deserializing)] - pub key_id: String, + #[schema(value_type = String)] + pub key_id: common_utils::id_type::ApiKeyId, #[serde(skip_deserializing)] #[schema(value_type = String)] @@ -146,8 +147,8 @@ pub struct RevokeApiKeyResponse { pub merchant_id: common_utils::id_type::MerchantId, /// The identifier for the API Key. - #[schema(max_length = 64, example = "5hEEqkgJUyuxgSKGArHA4mWSnX")] - pub key_id: String, + #[schema(max_length = 64, example = "5hEEqkgJUyuxgSKGArHA4mWSnX", value_type = String)] + pub key_id: common_utils::id_type::ApiKeyId, /// Indicates whether the API key was revoked or not. #[schema(example = "true")] pub revoked: bool, @@ -211,7 +212,7 @@ mod never { { struct NeverVisitor; - impl<'de> serde::de::Visitor<'de> for NeverVisitor { + impl serde::de::Visitor<'_> for NeverVisitor { type Value = (); fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/crates/api_models/src/conditional_configs.rs b/crates/api_models/src/conditional_configs.rs index 555e7bd955f0..3aed34e47a76 100644 --- a/crates/api_models/src/conditional_configs.rs +++ b/crates/api_models/src/conditional_configs.rs @@ -92,7 +92,6 @@ pub struct ConditionalConfigReq { pub algorithm: Option>, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] - pub struct DecisionManagerRequest { pub name: Option, pub program: Option>, diff --git a/crates/api_models/src/connector_enums.rs b/crates/api_models/src/connector_enums.rs new file mode 100644 index 000000000000..3a3fe5e8f429 --- /dev/null +++ b/crates/api_models/src/connector_enums.rs @@ -0,0 +1,320 @@ +pub use common_enums::enums::{PaymentMethod, PayoutType}; +#[cfg(feature = "dummy_connector")] +use common_utils::errors; +use utoipa::ToSchema; + +/// A connector is an integration to fulfill payments +#[derive( + Clone, + Copy, + Debug, + Eq, + PartialEq, + ToSchema, + serde::Deserialize, + serde::Serialize, + strum::VariantNames, + strum::EnumIter, + strum::Display, + strum::EnumString, + Hash, +)] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum Connector { + Adyenplatform, + #[cfg(feature = "dummy_connector")] + #[serde(rename = "phonypay")] + #[strum(serialize = "phonypay")] + DummyConnector1, + #[cfg(feature = "dummy_connector")] + #[serde(rename = "fauxpay")] + #[strum(serialize = "fauxpay")] + DummyConnector2, + #[cfg(feature = "dummy_connector")] + #[serde(rename = "pretendpay")] + #[strum(serialize = "pretendpay")] + DummyConnector3, + #[cfg(feature = "dummy_connector")] + #[serde(rename = "stripe_test")] + #[strum(serialize = "stripe_test")] + DummyConnector4, + #[cfg(feature = "dummy_connector")] + #[serde(rename = "adyen_test")] + #[strum(serialize = "adyen_test")] + DummyConnector5, + #[cfg(feature = "dummy_connector")] + #[serde(rename = "checkout_test")] + #[strum(serialize = "checkout_test")] + DummyConnector6, + #[cfg(feature = "dummy_connector")] + #[serde(rename = "paypal_test")] + #[strum(serialize = "paypal_test")] + DummyConnector7, + Aci, + Adyen, + Airwallex, + // Amazonpay, + Authorizedotnet, + Bambora, + Bamboraapac, + Bankofamerica, + Billwerk, + Bitpay, + Bluesnap, + Boku, + Braintree, + Cashtocode, + Checkout, + Coinbase, + Cryptopay, + CtpMastercard, + Cybersource, + Datatrans, + Deutschebank, + Digitalvirgo, + Dlocal, + Ebanx, + Elavon, + Fiserv, + Fiservemea, + Fiuu, + Forte, + Globalpay, + Globepay, + Gocardless, + Gpayments, + Helcim, + // Inespay, + Iatapay, + Itaubank, + Jpmorgan, + Klarna, + Mifinity, + Mollie, + Multisafepay, + Netcetera, + Nexinets, + Nexixpay, + Nmi, + // Nomupay, + Noon, + Novalnet, + Nuvei, + // Opayo, added as template code for future usage + Opennode, + Paybox, + // Payeezy, As psync and rsync are not supported by this connector, it is added as template code for future usage + Payme, + Payone, + Paypal, + Payu, + Placetopay, + Powertranz, + Prophetpay, + Rapyd, + Razorpay, + // Redsys, + Shift4, + Square, + Stax, + Stripe, + Taxjar, + Threedsecureio, + //Thunes, + Trustpay, + Tsys, + // UnifiedAuthenticationService, + Volt, + Wellsfargo, + // Wellsfargopayout, + Wise, + Worldline, + Worldpay, + Signifyd, + Plaid, + Riskified, + // Xendit, + Zen, + Zsl, +} + +impl Connector { + #[cfg(feature = "payouts")] + pub fn supports_instant_payout(self, payout_method: Option) -> bool { + matches!( + (self, payout_method), + (Self::Paypal, Some(PayoutType::Wallet)) + | (_, Some(PayoutType::Card)) + | (Self::Adyenplatform, _) + ) + } + #[cfg(feature = "payouts")] + pub fn supports_create_recipient(self, payout_method: Option) -> bool { + matches!((self, payout_method), (_, Some(PayoutType::Bank))) + } + #[cfg(feature = "payouts")] + pub fn supports_payout_eligibility(self, payout_method: Option) -> bool { + matches!((self, payout_method), (_, Some(PayoutType::Card))) + } + #[cfg(feature = "payouts")] + pub fn is_payout_quote_call_required(self) -> bool { + matches!(self, Self::Wise) + } + #[cfg(feature = "payouts")] + pub fn supports_access_token_for_payout(self, payout_method: Option) -> bool { + matches!((self, payout_method), (Self::Paypal, _)) + } + #[cfg(feature = "payouts")] + pub fn supports_vendor_disburse_account_create_for_payout(self) -> bool { + matches!(self, Self::Stripe) + } + pub fn supports_access_token(self, payment_method: PaymentMethod) -> bool { + matches!( + (self, payment_method), + (Self::Airwallex, _) + | (Self::Deutschebank, _) + | (Self::Globalpay, _) + | (Self::Jpmorgan, _) + | (Self::Paypal, _) + | (Self::Payu, _) + | (Self::Trustpay, PaymentMethod::BankRedirect) + | (Self::Iatapay, _) + | (Self::Volt, _) + | (Self::Itaubank, _) + ) + } + pub fn supports_file_storage_module(self) -> bool { + matches!(self, Self::Stripe | Self::Checkout) + } + pub fn requires_defend_dispute(self) -> bool { + matches!(self, Self::Checkout) + } + pub fn is_separate_authentication_supported(self) -> bool { + match self { + #[cfg(feature = "dummy_connector")] + Self::DummyConnector1 + | Self::DummyConnector2 + | Self::DummyConnector3 + | Self::DummyConnector4 + | Self::DummyConnector5 + | Self::DummyConnector6 + | Self::DummyConnector7 => false, + Self::Aci + // Add Separate authentication support for connectors + | Self::Adyen + | Self::Adyenplatform + | Self::Airwallex + // | Self::Amazonpay + | Self::Authorizedotnet + | Self::Bambora + | Self::Bamboraapac + | Self::Bankofamerica + | Self::Billwerk + | Self::Bitpay + | Self::Bluesnap + | Self::Boku + | Self::Braintree + | Self::Cashtocode + | Self::Coinbase + | Self::Cryptopay + | Self::Deutschebank + | Self::Digitalvirgo + | Self::Dlocal + | Self::Ebanx + | Self::Elavon + | Self::Fiserv + | Self::Fiservemea + | Self::Fiuu + | Self::Forte + | Self::Globalpay + | Self::Globepay + | Self::Gocardless + | Self::Gpayments + | Self::Helcim + | Self::Iatapay + // | Self::Inespay + | Self::Itaubank + | Self::Jpmorgan + | Self::Klarna + | Self::Mifinity + | Self::Mollie + | Self::Multisafepay + | Self::Nexinets + | Self::Nexixpay + // | Self::Nomupay + | Self::Novalnet + | Self::Nuvei + | Self::Opennode + | Self::Paybox + | Self::Payme + | Self::Payone + | Self::Paypal + | Self::Payu + | Self::Placetopay + | Self::Powertranz + | Self::Prophetpay + | Self::Rapyd + // | Self::Redsys + | Self::Shift4 + | Self::Square + | Self::Stax + | Self::Taxjar + // | Self::Thunes + | Self::Trustpay + | Self::Tsys + // | Self::UnifiedAuthenticationService + | Self::Volt + | Self::Wellsfargo + // | Self::Wellsfargopayout + | Self::Wise + | Self::Worldline + | Self::Worldpay + // | Self::Xendit + | Self::Zen + | Self::Zsl + | Self::Signifyd + | Self::Plaid + | Self::Razorpay + | Self::Riskified + | Self::Threedsecureio + | Self::Datatrans + | Self::Netcetera + | Self::CtpMastercard + | Self::Noon + | Self::Stripe => false, + Self::Checkout | Self::Nmi | Self::Cybersource => true, + } + } + pub fn is_pre_processing_required_before_authorize(self) -> bool { + matches!(self, Self::Airwallex) + } + pub fn should_acknowledge_webhook_for_resource_not_found_errors(self) -> bool { + matches!(self, Self::Adyenplatform) + } + #[cfg(feature = "dummy_connector")] + pub fn validate_dummy_connector_enabled( + self, + is_dummy_connector_enabled: bool, + ) -> errors::CustomResult<(), errors::ValidationError> { + if !is_dummy_connector_enabled + && matches!( + self, + Self::DummyConnector1 + | Self::DummyConnector2 + | Self::DummyConnector3 + | Self::DummyConnector4 + | Self::DummyConnector5 + | Self::DummyConnector6 + | Self::DummyConnector7 + ) + { + Err(errors::ValidationError::InvalidValue { + message: "Invalid connector name".to_string(), + } + .into()) + } else { + Ok(()) + } + } +} diff --git a/crates/api_models/src/connector_onboarding.rs b/crates/api_models/src/connector_onboarding.rs index 2dca57d5953f..e86f4a6f2fa9 100644 --- a/crates/api_models/src/connector_onboarding.rs +++ b/crates/api_models/src/connector_onboarding.rs @@ -42,7 +42,7 @@ pub enum PayPalOnboardingStatus { MorePermissionsNeeded, EmailNotVerified, Success(PayPalOnboardingDone), - ConnectorIntegrated(admin::MerchantConnectorResponse), + ConnectorIntegrated(Box), } #[derive(serde::Serialize, Debug, Clone)] diff --git a/crates/api_models/src/customers.rs b/crates/api_models/src/customers.rs index eb196a720746..fe4ba7d96862 100644 --- a/crates/api_models/src/customers.rs +++ b/crates/api_models/src/customers.rs @@ -1,12 +1,5 @@ -use common_utils::{ - crypto, custom_serde, - encryption::Encryption, - id_type, - pii::{self, EmailStrategy}, - types::{keymanager::ToEncryptable, Description}, -}; -use masking::{ExposeInterface, Secret, SwitchStrategy}; -use rustc_hash::FxHashMap; +use common_utils::{crypto, custom_serde, id_type, pii, types::Description}; +use masking::Secret; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; @@ -78,6 +71,7 @@ impl CustomerRequest { /// The customer details #[cfg(all(feature = "v2", feature = "customer_v2"))] #[derive(Debug, Default, Clone, Deserialize, Serialize, ToSchema)] +#[serde(deny_unknown_fields)] pub struct CustomerRequest { /// The merchant identifier for the customer object. #[schema(value_type = Option, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")] @@ -129,85 +123,6 @@ impl CustomerRequest { } } -pub struct CustomerRequestWithEmail { - pub name: Option>, - pub email: Option, - pub phone: Option>, -} - -pub struct CustomerRequestWithEncryption { - pub name: Option, - pub phone: Option, - pub email: Option, -} - -pub struct EncryptableCustomer { - pub name: crypto::OptionalEncryptableName, - pub phone: crypto::OptionalEncryptablePhone, - pub email: crypto::OptionalEncryptableEmail, -} - -impl ToEncryptable, Encryption> - for CustomerRequestWithEncryption -{ - fn to_encryptable(self) -> FxHashMap { - let mut map = FxHashMap::with_capacity_and_hasher(3, Default::default()); - self.name.map(|x| map.insert("name".to_string(), x)); - self.phone.map(|x| map.insert("phone".to_string(), x)); - self.email.map(|x| map.insert("email".to_string(), x)); - map - } - - fn from_encryptable( - mut hashmap: FxHashMap>>, - ) -> common_utils::errors::CustomResult - { - Ok(EncryptableCustomer { - name: hashmap.remove("name"), - phone: hashmap.remove("phone"), - email: hashmap.remove("email").map(|email| { - let encryptable: crypto::Encryptable> = - crypto::Encryptable::new( - email.clone().into_inner().switch_strategy(), - email.into_encrypted(), - ); - encryptable - }), - }) - } -} - -impl ToEncryptable, Secret> - for CustomerRequestWithEmail -{ - fn to_encryptable(self) -> FxHashMap> { - let mut map = FxHashMap::with_capacity_and_hasher(3, Default::default()); - self.name.map(|x| map.insert("name".to_string(), x)); - self.phone.map(|x| map.insert("phone".to_string(), x)); - self.email - .map(|x| map.insert("email".to_string(), x.expose().switch_strategy())); - map - } - - fn from_encryptable( - mut hashmap: FxHashMap>>, - ) -> common_utils::errors::CustomResult - { - Ok(EncryptableCustomer { - name: hashmap.remove("name"), - email: hashmap.remove("email").map(|email| { - let encryptable: crypto::Encryptable> = - crypto::Encryptable::new( - email.clone().into_inner().switch_strategy(), - email.into_encrypted(), - ); - encryptable - }), - phone: hashmap.remove("phone"), - }) - } -} - #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] #[derive(Debug, Clone, Serialize, ToSchema)] pub struct CustomerResponse { @@ -256,6 +171,14 @@ impl CustomerResponse { #[cfg(all(feature = "v2", feature = "customer_v2"))] #[derive(Debug, Clone, Serialize, ToSchema)] pub struct CustomerResponse { + /// Unique identifier for the customer + #[schema( + min_length = 32, + max_length = 64, + example = "12345_cus_01926c58bc6e77c09e809964e72af8c8", + value_type = String + )] + pub id: id_type::GlobalCustomerId, /// The identifier for the customer object #[schema(value_type = String, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")] pub merchant_reference_id: Option, @@ -292,8 +215,6 @@ pub struct CustomerResponse { /// The identifier for the default payment method. #[schema(max_length = 64, example = "pm_djh2837dwduh890123")] pub default_payment_method_id: Option, - /// Global id - pub id: String, } #[cfg(all(feature = "v2", feature = "customer_v2"))] @@ -303,55 +224,6 @@ impl CustomerResponse { } } -#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct CustomerId { - pub customer_id: id_type::CustomerId, -} - -#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] -impl CustomerId { - pub fn get_merchant_reference_id(&self) -> id_type::CustomerId { - self.customer_id.clone() - } - - pub fn new_customer_id_struct(cust: id_type::CustomerId) -> Self { - Self { customer_id: cust } - } -} - -#[cfg(all(feature = "v2", feature = "customer_v2"))] -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct GlobalId { - pub id: String, -} - -#[cfg(all(feature = "v2", feature = "customer_v2"))] -impl GlobalId { - pub fn new(id: String) -> Self { - Self { id } - } -} - -#[cfg(all(feature = "v2", feature = "customer_v2"))] -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct CustomerId { - pub merchant_reference_id: id_type::CustomerId, -} - -#[cfg(all(feature = "v2", feature = "customer_v2"))] -impl CustomerId { - pub fn get_merchant_reference_id(&self) -> id_type::CustomerId { - self.merchant_reference_id.clone() - } - - pub fn new_customer_id_struct(cust: id_type::CustomerId) -> Self { - Self { - merchant_reference_id: cust, - } - } -} - #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] #[derive(Debug, Deserialize, Serialize, ToSchema)] pub struct CustomerDeleteResponse { @@ -372,6 +244,14 @@ pub struct CustomerDeleteResponse { #[cfg(all(feature = "v2", feature = "customer_v2"))] #[derive(Debug, Deserialize, Serialize, ToSchema)] pub struct CustomerDeleteResponse { + /// Unique identifier for the customer + #[schema( + min_length = 32, + max_length = 64, + example = "12345_cus_01926c58bc6e77c09e809964e72af8c8", + value_type = String + )] + pub id: id_type::GlobalCustomerId, /// The identifier for the customer object #[schema(value_type = String, max_length = 255, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")] pub merchant_reference_id: Option, @@ -384,17 +264,12 @@ pub struct CustomerDeleteResponse { /// Whether payment methods deleted or not #[schema(example = false)] pub payment_methods_deleted: bool, - /// Global id - pub id: String, } /// The identifier for the customer object. If not provided the customer ID will be autogenerated. #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] #[derive(Debug, Default, Clone, Deserialize, Serialize, ToSchema)] pub struct CustomerUpdateRequest { - /// The identifier for the customer object - #[schema(value_type = Option, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")] - pub customer_id: Option, /// The identifier for the Merchant Account #[schema(max_length = 255, example = "y3oqhf46pyzuxjbcn2giaqnb44")] #[serde(skip)] @@ -426,13 +301,6 @@ pub struct CustomerUpdateRequest { #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] impl CustomerUpdateRequest { - pub fn get_merchant_reference_id(&self) -> Option { - Some( - self.customer_id - .to_owned() - .unwrap_or_else(common_utils::generate_customer_id_of_default_length), - ) - } pub fn get_address(&self) -> Option { self.address.clone() } @@ -440,6 +308,7 @@ impl CustomerUpdateRequest { #[cfg(all(feature = "v2", feature = "customer_v2"))] #[derive(Debug, Default, Clone, Deserialize, Serialize, ToSchema)] +#[serde(deny_unknown_fields)] pub struct CustomerUpdateRequest { /// The merchant identifier for the customer object. #[schema(value_type = Option, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")] @@ -490,15 +359,16 @@ impl CustomerUpdateRequest { } } -#[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone)] -pub struct UpdateCustomerId(String); - -impl UpdateCustomerId { - pub fn get_global_id(&self) -> String { - self.0.clone() - } +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] +#[derive(Debug, Serialize)] +pub struct CustomerUpdateRequestInternal { + pub customer_id: id_type::CustomerId, + pub request: CustomerUpdateRequest, +} - pub fn new(id: String) -> Self { - Self(id) - } +#[cfg(all(feature = "v2", feature = "customer_v2"))] +#[derive(Debug, Serialize)] +pub struct CustomerUpdateRequestInternal { + pub id: id_type::GlobalCustomerId, + pub request: CustomerUpdateRequest, } diff --git a/crates/api_models/src/disputes.rs b/crates/api_models/src/disputes.rs index 4ddb50af0f2a..5b0de11f2d05 100644 --- a/crates/api_models/src/disputes.rs +++ b/crates/api_models/src/disputes.rs @@ -6,8 +6,8 @@ use serde::de::Error; use time::PrimitiveDateTime; use utoipa::ToSchema; -use super::enums::{DisputeStage, DisputeStatus}; -use crate::{admin::MerchantConnectorInfo, enums, files}; +use super::enums::{Currency, DisputeStage, DisputeStatus}; +use crate::{admin::MerchantConnectorInfo, files}; #[derive(Clone, Debug, Serialize, ToSchema, Eq, PartialEq)] pub struct DisputeResponse { @@ -21,7 +21,8 @@ pub struct DisputeResponse { /// The dispute amount pub amount: String, /// The three-letter ISO currency code - pub currency: String, + #[schema(value_type = Currency)] + pub currency: Currency, /// Stage of the dispute pub dispute_stage: DisputeStage, /// Status of the dispute @@ -137,7 +138,7 @@ pub struct DisputeListGetConstraints { pub connector: Option>, /// The comma separated list of currencies of the disputes #[serde(default, deserialize_with = "parse_comma_separated")] - pub currency: Option>, + pub currency: Option>, /// The merchant connector id to filter the disputes list pub merchant_connector_id: Option, /// The time range for which objects are needed. TimeRange has two fields start_time and end_time from which objects can be filtered as per required scenarios (created_at, time less than, greater than etc). @@ -150,7 +151,7 @@ pub struct DisputeListFilters { /// The map of available connector filters, where the key is the connector name and the value is a list of MerchantConnectorInfo instances pub connector: HashMap>, /// The list of available currency filters - pub currency: Vec, + pub currency: Vec, /// The list of available dispute status filters pub dispute_status: Vec, /// The list of available dispute stage filters diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index bb2860acb03d..f3c3f242450f 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -1,10 +1,10 @@ use std::str::FromStr; pub use common_enums::*; -#[cfg(feature = "dummy_connector")] -use common_utils::errors; use utoipa::ToSchema; +pub use super::connector_enums::Connector; + #[derive( Clone, Copy, @@ -27,299 +27,6 @@ pub enum RoutingAlgorithm { Custom, } -/// A connector is an integration to fulfill payments -#[derive( - Clone, - Copy, - Debug, - Eq, - PartialEq, - ToSchema, - serde::Deserialize, - serde::Serialize, - strum::VariantNames, - strum::EnumIter, - strum::Display, - strum::EnumString, - Hash, -)] -#[serde(rename_all = "snake_case")] -#[strum(serialize_all = "snake_case")] -pub enum Connector { - // Nexixpay, - Adyenplatform, - #[cfg(feature = "dummy_connector")] - #[serde(rename = "phonypay")] - #[strum(serialize = "phonypay")] - DummyConnector1, - #[cfg(feature = "dummy_connector")] - #[serde(rename = "fauxpay")] - #[strum(serialize = "fauxpay")] - DummyConnector2, - #[cfg(feature = "dummy_connector")] - #[serde(rename = "pretendpay")] - #[strum(serialize = "pretendpay")] - DummyConnector3, - #[cfg(feature = "dummy_connector")] - #[serde(rename = "stripe_test")] - #[strum(serialize = "stripe_test")] - DummyConnector4, - #[cfg(feature = "dummy_connector")] - #[serde(rename = "adyen_test")] - #[strum(serialize = "adyen_test")] - DummyConnector5, - #[cfg(feature = "dummy_connector")] - #[serde(rename = "checkout_test")] - #[strum(serialize = "checkout_test")] - DummyConnector6, - #[cfg(feature = "dummy_connector")] - #[serde(rename = "paypal_test")] - #[strum(serialize = "paypal_test")] - DummyConnector7, - Aci, - Adyen, - Airwallex, - Authorizedotnet, - Bambora, - Bamboraapac, - Bankofamerica, - Billwerk, - Bitpay, - Bluesnap, - Boku, - Braintree, - Cashtocode, - Checkout, - Coinbase, - Cryptopay, - Cybersource, - Datatrans, - Deutschebank, - Dlocal, - Ebanx, - Fiserv, - Fiservemea, - Fiuu, - Forte, - Globalpay, - Globepay, - Gocardless, - Gpayments, - Helcim, - Iatapay, - Itaubank, - Klarna, - Mifinity, - Mollie, - Multisafepay, - Netcetera, - Nexinets, - Nmi, - Noon, - Novalnet, - Nuvei, - // Opayo, added as template code for future usage - Opennode, - Paybox, - // Payeezy, As psync and rsync are not supported by this connector, it is added as template code for future usage - Payme, - Payone, - Paypal, - Payu, - Placetopay, - Powertranz, - Prophetpay, - Rapyd, - Razorpay, - Shift4, - Square, - Stax, - Stripe, - Taxjar, - Threedsecureio, - //Thunes, - Trustpay, - Tsys, - Volt, - Wellsfargo, - // Wellsfargopayout, - Wise, - Worldline, - Worldpay, - Signifyd, - Plaid, - Riskified, - Zen, - Zsl, -} - -impl Connector { - #[cfg(feature = "payouts")] - pub fn supports_instant_payout(&self, payout_method: Option) -> bool { - matches!( - (self, payout_method), - (Self::Paypal, Some(PayoutType::Wallet)) - | (_, Some(PayoutType::Card)) - | (Self::Adyenplatform, _) - ) - } - #[cfg(feature = "payouts")] - pub fn supports_create_recipient(&self, payout_method: Option) -> bool { - matches!((self, payout_method), (_, Some(PayoutType::Bank))) - } - #[cfg(feature = "payouts")] - pub fn supports_payout_eligibility(&self, payout_method: Option) -> bool { - matches!((self, payout_method), (_, Some(PayoutType::Card))) - } - #[cfg(feature = "payouts")] - pub fn is_payout_quote_call_required(&self) -> bool { - matches!(self, Self::Wise) - } - #[cfg(feature = "payouts")] - pub fn supports_access_token_for_payout(&self, payout_method: Option) -> bool { - matches!((self, payout_method), (Self::Paypal, _)) - } - #[cfg(feature = "payouts")] - pub fn supports_vendor_disburse_account_create_for_payout(&self) -> bool { - matches!(self, Self::Stripe) - } - pub fn supports_access_token(&self, payment_method: PaymentMethod) -> bool { - matches!( - (self, payment_method), - (Self::Airwallex, _) - | (Self::Deutschebank, _) - | (Self::Globalpay, _) - | (Self::Paypal, _) - | (Self::Payu, _) - | (Self::Trustpay, PaymentMethod::BankRedirect) - | (Self::Iatapay, _) - | (Self::Volt, _) - | (Self::Itaubank, _) - ) - } - pub fn supports_file_storage_module(&self) -> bool { - matches!(self, Self::Stripe | Self::Checkout) - } - pub fn requires_defend_dispute(&self) -> bool { - matches!(self, Self::Checkout) - } - pub fn is_separate_authentication_supported(&self) -> bool { - match self { - #[cfg(feature = "dummy_connector")] - Self::DummyConnector1 - | Self::DummyConnector2 - | Self::DummyConnector3 - | Self::DummyConnector4 - | Self::DummyConnector5 - | Self::DummyConnector6 - | Self::DummyConnector7 => false, - Self::Aci - // Add Separate authentication support for connectors - // | Self::Nexixpay - // | Self::Fiuu - | Self::Adyen - | Self::Adyenplatform - | Self::Airwallex - | Self::Authorizedotnet - | Self::Bambora - | Self::Bamboraapac - | Self::Bankofamerica - | Self::Billwerk - | Self::Bitpay - | Self::Bluesnap - | Self::Boku - | Self::Braintree - | Self::Cashtocode - | Self::Coinbase - | Self::Cryptopay - | Self::Deutschebank - | Self::Dlocal - | Self::Ebanx - | Self::Fiserv - | Self::Fiservemea - | Self::Fiuu - | Self::Forte - | Self::Globalpay - | Self::Globepay - | Self::Gocardless - | Self::Gpayments - | Self::Helcim - | Self::Iatapay - | Self::Itaubank - | Self::Klarna - | Self::Mifinity - | Self::Mollie - | Self::Multisafepay - | Self::Nexinets - | Self::Novalnet - | Self::Nuvei - | Self::Opennode - | Self::Paybox - | Self::Payme - | Self::Payone - | Self::Paypal - | Self::Payu - | Self::Placetopay - | Self::Powertranz - | Self::Prophetpay - | Self::Rapyd - | Self::Shift4 - | Self::Square - | Self::Stax - | Self::Taxjar - //| Self::Thunes - | Self::Trustpay - | Self::Tsys - | Self::Volt - | Self::Wellsfargo - // | Self::Wellsfargopayout - | Self::Wise - | Self::Worldline - | Self::Worldpay - | Self::Zen - | Self::Zsl - | Self::Signifyd - | Self::Plaid - | Self::Razorpay - | Self::Riskified - | Self::Threedsecureio - | Self::Datatrans - | Self::Netcetera - | Self::Noon - | Self::Stripe => false, - Self::Checkout | Self::Nmi | Self::Cybersource => true, - } - } - pub fn is_pre_processing_required_before_authorize(&self) -> bool { - matches!(self, Self::Airwallex) - } - #[cfg(feature = "dummy_connector")] - pub fn validate_dummy_connector_enabled( - &self, - is_dummy_connector_enabled: bool, - ) -> errors::CustomResult<(), errors::ValidationError> { - if !is_dummy_connector_enabled - && matches!( - self, - Self::DummyConnector1 - | Self::DummyConnector2 - | Self::DummyConnector3 - | Self::DummyConnector4 - | Self::DummyConnector5 - | Self::DummyConnector6 - | Self::DummyConnector7 - ) - { - Err(errors::ValidationError::InvalidValue { - message: "Invalid connector name".to_string(), - } - .into()) - } else { - Ok(()) - } - } -} - #[cfg(feature = "payouts")] #[derive( Clone, @@ -434,7 +141,6 @@ pub enum FrmConnectors { )] #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] - pub enum TaxConnectors { Taxjar, } @@ -517,8 +223,12 @@ pub enum FieldType { UserCpf, UserCnpj, UserIban, - BrowserLanguage, - BrowserIp, + UserBsbNumber, + UserBankSortCode, + UserBankRoutingNumber, + UserMsisdn, + UserClientIdentifier, + OrderDetailsProductName, } impl FieldType { @@ -609,6 +319,9 @@ impl PartialEq for FieldType { (Self::UserCpf, Self::UserCpf) => true, (Self::UserCnpj, Self::UserCnpj) => true, (Self::LanguagePreference { .. }, Self::LanguagePreference { .. }) => true, + (Self::UserMsisdn, Self::UserMsisdn) => true, + (Self::UserClientIdentifier, Self::UserClientIdentifier) => true, + (Self::OrderDetailsProductName, Self::OrderDetailsProductName) => true, _unused => false, } } @@ -688,51 +401,24 @@ pub fn convert_tax_connector(connector_name: &str) -> Option { TaxConnectors::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()) - } +#[cfg(feature = "frm")] +pub fn convert_frm_connector(connector_name: &str) -> Option { + FrmConnectors::from_str(connector_name).ok() } -#[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, +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, serde::Serialize, Hash)] +pub enum ReconPermissionScope { + #[serde(rename = "R")] + Read = 0, + #[serde(rename = "RW")] + Write = 1, } -#[cfg(feature = "frm")] -pub fn convert_frm_connector(connector_name: &str) -> Option { - FrmConnectors::from_str(connector_name).ok() +impl From for ReconPermissionScope { + fn from(scope: PermissionScope) -> Self { + match scope { + PermissionScope::Read => Self::Read, + PermissionScope::Write => Self::Write, + } + } } diff --git a/crates/api_models/src/ephemeral_key.rs b/crates/api_models/src/ephemeral_key.rs index d7ee7bd25171..fa61642e353a 100644 --- a/crates/api_models/src/ephemeral_key.rs +++ b/crates/api_models/src/ephemeral_key.rs @@ -1,7 +1,69 @@ use common_utils::id_type; +#[cfg(feature = "v2")] +use masking::Secret; use serde; use utoipa::ToSchema; +#[cfg(feature = "v1")] +/// Information required to create an ephemeral key. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct EphemeralKeyCreateRequest { + /// Customer ID for which an ephemeral key must be created + #[schema( + min_length = 1, + max_length = 64, + value_type = String, + example = "cus_y3oqhf46pyzuxjbcn2giaqnb44" + )] + pub customer_id: id_type::CustomerId, +} + +#[cfg(feature = "v2")] +/// Information required to create an ephemeral key. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct EphemeralKeyCreateRequest { + /// Customer ID for which an ephemeral key must be created + #[schema( + min_length = 32, + max_length = 64, + value_type = String, + example = "12345_cus_01926c58bc6e77c09e809964e72af8c8" + )] + pub customer_id: id_type::GlobalCustomerId, +} + +#[cfg(feature = "v2")] +/// ephemeral_key for the customer_id mentioned +#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Eq, PartialEq, ToSchema)] +pub struct EphemeralKeyResponse { + /// Ephemeral key id + #[schema(value_type = String, max_length = 32, min_length = 1)] + pub id: id_type::EphemeralKeyId, + /// customer_id to which this ephemeral key belongs to + #[schema(value_type = String, max_length = 64, min_length = 32, example = "12345_cus_01926c58bc6e77c09e809964e72af8c8")] + pub customer_id: id_type::GlobalCustomerId, + /// time at which this ephemeral key was created + pub created_at: time::PrimitiveDateTime, + /// time at which this ephemeral key would expire + pub expires: time::PrimitiveDateTime, + #[schema(value_type=String)] + /// ephemeral key + pub secret: Secret, +} + +impl common_utils::events::ApiEventMetric for EphemeralKeyCreateRequest { + fn get_api_event_type(&self) -> Option { + Some(common_utils::events::ApiEventsType::Miscellaneous) + } +} + +#[cfg(feature = "v2")] +impl common_utils::events::ApiEventMetric for EphemeralKeyResponse { + fn get_api_event_type(&self) -> Option { + Some(common_utils::events::ApiEventsType::Miscellaneous) + } +} + /// ephemeral_key for the customer_id mentioned #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Eq, PartialEq, ToSchema)] pub struct EphemeralKeyCreateResponse { diff --git a/crates/api_models/src/errors/mod.rs b/crates/api_models/src/errors.rs similarity index 100% rename from crates/api_models/src/errors/mod.rs rename to crates/api_models/src/errors.rs diff --git a/crates/api_models/src/events.rs b/crates/api_models/src/events.rs index 5816df518bdd..72a0d592513f 100644 --- a/crates/api_models/src/events.rs +++ b/crates/api_models/src/events.rs @@ -131,6 +131,7 @@ impl_api_event_type!( GetDisputeFilterRequest, DisputeFiltersResponse, GetDisputeMetricRequest, + SankeyResponse, OrganizationResponse, OrganizationCreateRequest, OrganizationUpdateRequest, @@ -155,13 +156,36 @@ impl ApiEventMetric for MetricsResponse { } } +impl ApiEventMetric for PaymentsMetricsResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Miscellaneous) + } +} + +impl ApiEventMetric for PaymentIntentsMetricsResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Miscellaneous) + } +} + +impl ApiEventMetric for RefundsMetricsResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Miscellaneous) + } +} + +impl ApiEventMetric for DisputesMetricsResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Miscellaneous) + } +} #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] impl ApiEventMetric for PaymentMethodIntentConfirmInternal { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::PaymentMethod { payment_method_id: self.id.clone(), - payment_method: Some(self.payment_method), - payment_method_type: Some(self.payment_method_type), + payment_method: Some(self.payment_method_type), + payment_method_type: Some(self.payment_method_subtype), }) } } diff --git a/crates/api_models/src/events/customer.rs b/crates/api_models/src/events/customer.rs index d2b30bcf0100..55a55fbcecdc 100644 --- a/crates/api_models/src/events/customer.rs +++ b/crates/api_models/src/events/customer.rs @@ -1,11 +1,7 @@ use common_utils::events::{ApiEventMetric, ApiEventsType}; -#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] -use crate::customers::CustomerId; -#[cfg(all(feature = "v2", feature = "customer_v2"))] -use crate::customers::GlobalId; use crate::customers::{ - CustomerDeleteResponse, CustomerRequest, CustomerResponse, CustomerUpdateRequest, + CustomerDeleteResponse, CustomerRequest, CustomerResponse, CustomerUpdateRequestInternal, }; #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] @@ -21,7 +17,7 @@ impl ApiEventMetric for CustomerDeleteResponse { impl ApiEventMetric for CustomerDeleteResponse { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Customer { - id: self.id.clone(), + customer_id: Some(self.id.clone()), }) } } @@ -38,9 +34,7 @@ impl ApiEventMetric for CustomerRequest { #[cfg(all(feature = "v2", feature = "customer_v2"))] impl ApiEventMetric for CustomerRequest { fn get_api_event_type(&self) -> Option { - Some(ApiEventsType::Customer { - id: "temp_id".to_string(), - }) + Some(ApiEventsType::Customer { customer_id: None }) } } @@ -57,44 +51,25 @@ impl ApiEventMetric for CustomerResponse { impl ApiEventMetric for CustomerResponse { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Customer { - id: self.id.clone(), + customer_id: Some(self.id.clone()), }) } } #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] -impl ApiEventMetric for CustomerId { - fn get_api_event_type(&self) -> Option { - Some(ApiEventsType::Customer { - customer_id: self.get_merchant_reference_id().clone(), - }) - } -} - -#[cfg(all(feature = "v2", feature = "customer_v2"))] -impl ApiEventMetric for GlobalId { +impl ApiEventMetric for CustomerUpdateRequestInternal { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Customer { - id: self.id.clone(), + customer_id: self.customer_id.clone(), }) } } -#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] -impl ApiEventMetric for CustomerUpdateRequest { - fn get_api_event_type(&self) -> Option { - self.get_merchant_reference_id() - .clone() - .map(|cid| ApiEventsType::Customer { customer_id: cid }) - } -} - #[cfg(all(feature = "v2", feature = "customer_v2"))] -impl ApiEventMetric for CustomerUpdateRequest { +impl ApiEventMetric for CustomerUpdateRequestInternal { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Customer { - id: "temo_id".to_string(), + customer_id: Some(self.id.clone()), }) } } -// These needs to be fixed for v2 diff --git a/crates/api_models/src/events/payment.rs b/crates/api_models/src/events/payment.rs index aa8cd43ca97f..c242788e090b 100644 --- a/crates/api_models/src/events/payment.rs +++ b/crates/api_models/src/events/payment.rs @@ -1,33 +1,41 @@ use common_utils::events::{ApiEventMetric, ApiEventsType}; +#[cfg(feature = "v2")] +use super::{ + PaymentStartRedirectionRequest, PaymentsConfirmIntentResponse, PaymentsCreateIntentRequest, + PaymentsGetIntentRequest, PaymentsIntentResponse, +}; #[cfg(all( any(feature = "v2", feature = "v1"), not(feature = "payment_methods_v2") ))] use crate::payment_methods::CustomerPaymentMethodsListResponse; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -use crate::payment_methods::CustomerPaymentMethodsListResponse; +use crate::{events, payment_methods::CustomerPaymentMethodsListResponse}; use crate::{ payment_methods::{ CustomerDefaultPaymentMethodResponse, DefaultPaymentMethod, ListCountriesCurrenciesRequest, ListCountriesCurrenciesResponse, PaymentMethodCollectLinkRenderRequest, PaymentMethodCollectLinkRequest, PaymentMethodCollectLinkResponse, PaymentMethodDeleteResponse, PaymentMethodListRequest, PaymentMethodListResponse, - PaymentMethodResponse, PaymentMethodUpdate, + PaymentMethodMigrateResponse, PaymentMethodResponse, PaymentMethodUpdate, }, payments::{ - ExtendedCardInfoResponse, PaymentIdType, PaymentListConstraints, + self, ExtendedCardInfoResponse, PaymentIdType, PaymentListConstraints, PaymentListFilterConstraints, PaymentListFilters, PaymentListFiltersV2, PaymentListResponse, PaymentListResponseV2, PaymentsAggregateResponse, PaymentsApproveRequest, PaymentsCancelRequest, PaymentsCaptureRequest, PaymentsCompleteAuthorizeRequest, PaymentsDynamicTaxCalculationRequest, PaymentsDynamicTaxCalculationResponse, PaymentsExternalAuthenticationRequest, PaymentsExternalAuthenticationResponse, PaymentsIncrementalAuthorizationRequest, - PaymentsManualUpdateRequest, PaymentsManualUpdateResponse, PaymentsRejectRequest, - PaymentsRequest, PaymentsResponse, PaymentsRetrieveRequest, PaymentsSessionResponse, - PaymentsStartRequest, RedirectionResponse, + PaymentsManualUpdateRequest, PaymentsManualUpdateResponse, + PaymentsPostSessionTokensRequest, PaymentsPostSessionTokensResponse, PaymentsRejectRequest, + PaymentsResponse, PaymentsRetrieveRequest, PaymentsSessionResponse, PaymentsStartRequest, + RedirectionResponse, }, }; + +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsRetrieveRequest { fn get_api_event_type(&self) -> Option { match self.resource_id { @@ -39,6 +47,7 @@ impl ApiEventMetric for PaymentsRetrieveRequest { } } +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsStartRequest { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { @@ -47,6 +56,7 @@ impl ApiEventMetric for PaymentsStartRequest { } } +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsCaptureRequest { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { @@ -55,6 +65,7 @@ impl ApiEventMetric for PaymentsCaptureRequest { } } +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsCompleteAuthorizeRequest { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { @@ -63,6 +74,7 @@ impl ApiEventMetric for PaymentsCompleteAuthorizeRequest { } } +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsDynamicTaxCalculationRequest { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { @@ -71,8 +83,28 @@ impl ApiEventMetric for PaymentsDynamicTaxCalculationRequest { } } +#[cfg(feature = "v1")] +impl ApiEventMetric for PaymentsPostSessionTokensRequest { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Payment { + payment_id: self.payment_id.clone(), + }) + } +} + +#[cfg(feature = "v1")] +impl ApiEventMetric for PaymentsPostSessionTokensResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Payment { + payment_id: self.payment_id.clone(), + }) + } +} + +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsDynamicTaxCalculationResponse {} +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsCancelRequest { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { @@ -81,6 +113,7 @@ impl ApiEventMetric for PaymentsCancelRequest { } } +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsApproveRequest { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { @@ -89,6 +122,7 @@ impl ApiEventMetric for PaymentsApproveRequest { } } +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsRejectRequest { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { @@ -97,7 +131,8 @@ impl ApiEventMetric for PaymentsRejectRequest { } } -impl ApiEventMetric for PaymentsRequest { +#[cfg(feature = "v1")] +impl ApiEventMetric for payments::PaymentsRequest { fn get_api_event_type(&self) -> Option { match self.payment_id { Some(PaymentIdType::PaymentIntentId(ref id)) => Some(ApiEventsType::Payment { @@ -108,6 +143,50 @@ impl ApiEventMetric for PaymentsRequest { } } +#[cfg(feature = "v2")] +impl ApiEventMetric for PaymentsCreateIntentRequest { + fn get_api_event_type(&self) -> Option { + None + } +} + +#[cfg(feature = "v2")] +impl ApiEventMetric for PaymentsGetIntentRequest { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Payment { + payment_id: self.id.clone(), + }) + } +} + +#[cfg(feature = "v2")] +impl ApiEventMetric for PaymentsIntentResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Payment { + payment_id: self.id.clone(), + }) + } +} + +#[cfg(feature = "v2")] +impl ApiEventMetric for PaymentsConfirmIntentResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Payment { + payment_id: self.id.clone(), + }) + } +} + +#[cfg(feature = "v2")] +impl ApiEventMetric for super::PaymentsRetrieveResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Payment { + payment_id: self.id.clone(), + }) + } +} + +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsResponse { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { @@ -117,6 +196,10 @@ impl ApiEventMetric for PaymentsResponse { } impl ApiEventMetric for PaymentMethodResponse { + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") + ))] fn get_api_event_type(&self) -> Option { Some(ApiEventsType::PaymentMethod { payment_method_id: self.payment_method_id.clone(), @@ -124,6 +207,38 @@ impl ApiEventMetric for PaymentMethodResponse { payment_method_type: self.payment_method_type, }) } + + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::PaymentMethod { + payment_method_id: self.payment_method_id.clone(), + payment_method: self.payment_method_type, + payment_method_type: self.payment_method_subtype, + }) + } +} + +impl ApiEventMetric for PaymentMethodMigrateResponse { + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") + ))] + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::PaymentMethod { + payment_method_id: self.payment_method_response.payment_method_id.clone(), + payment_method: self.payment_method_response.payment_method, + payment_method_type: self.payment_method_response.payment_method_type, + }) + } + + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::PaymentMethod { + payment_method_id: self.payment_method_response.payment_method_id.clone(), + payment_method: self.payment_method_response.payment_method_type, + payment_method_type: self.payment_method_response.payment_method_subtype, + }) + } } impl ApiEventMetric for PaymentMethodUpdate {} @@ -245,6 +360,7 @@ impl ApiEventMetric for PaymentsAggregateResponse { impl ApiEventMetric for RedirectionResponse {} +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsIncrementalAuthorizationRequest { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { @@ -253,8 +369,10 @@ impl ApiEventMetric for PaymentsIncrementalAuthorizationRequest { } } +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsExternalAuthenticationResponse {} +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsExternalAuthenticationRequest { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { @@ -263,8 +381,10 @@ impl ApiEventMetric for PaymentsExternalAuthenticationRequest { } } +#[cfg(feature = "v1")] impl ApiEventMetric for ExtendedCardInfoResponse {} +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsManualUpdateRequest { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { @@ -273,6 +393,7 @@ impl ApiEventMetric for PaymentsManualUpdateRequest { } } +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentsManualUpdateResponse { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { @@ -288,3 +409,29 @@ impl ApiEventMetric for PaymentsSessionResponse { }) } } + +#[cfg(feature = "v2")] +impl ApiEventMetric for PaymentStartRedirectionRequest { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Payment { + payment_id: self.id.clone(), + }) + } +} + +#[cfg(feature = "v2")] +impl ApiEventMetric for payments::PaymentMethodListResponseForPayments { + // Payment id would be populated by the request + fn get_api_event_type(&self) -> Option { + None + } +} + +#[cfg(feature = "v2")] +impl ApiEventMetric for payments::PaymentsCaptureResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Payment { + payment_id: self.id.clone(), + }) + } +} diff --git a/crates/api_models/src/events/recon.rs b/crates/api_models/src/events/recon.rs index aed648f4c869..596b05412827 100644 --- a/crates/api_models/src/events/recon.rs +++ b/crates/api_models/src/events/recon.rs @@ -1,6 +1,9 @@ use common_utils::events::{ApiEventMetric, ApiEventsType}; +use masking::PeekInterface; -use crate::recon::{ReconStatusResponse, ReconTokenResponse, ReconUpdateMerchantRequest}; +use crate::recon::{ + ReconStatusResponse, ReconTokenResponse, ReconUpdateMerchantRequest, VerifyTokenResponse, +}; impl ApiEventMetric for ReconUpdateMerchantRequest { fn get_api_event_type(&self) -> Option { @@ -19,3 +22,11 @@ impl ApiEventMetric for ReconStatusResponse { Some(ApiEventsType::Recon) } } + +impl ApiEventMetric for VerifyTokenResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::User { + user_id: self.user_email.peek().to_string(), + }) + } +} diff --git a/crates/api_models/src/events/refund.rs b/crates/api_models/src/events/refund.rs index d180753735f3..e87ae1544714 100644 --- a/crates/api_models/src/events/refund.rs +++ b/crates/api_models/src/events/refund.rs @@ -6,6 +6,7 @@ use crate::refunds::{ RefundUpdateRequest, RefundsRetrieveRequest, }; +#[cfg(feature = "v1")] impl ApiEventMetric for RefundRequest { fn get_api_event_type(&self) -> Option { let payment_id = self.payment_id.clone(); @@ -18,6 +19,7 @@ impl ApiEventMetric for RefundRequest { } } +#[cfg(feature = "v1")] impl ApiEventMetric for RefundResponse { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Refund { @@ -27,6 +29,17 @@ impl ApiEventMetric for RefundResponse { } } +#[cfg(feature = "v2")] +impl ApiEventMetric for RefundResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Refund { + payment_id: self.payment_id.clone(), + refund_id: self.id.clone(), + }) + } +} + +#[cfg(feature = "v1")] impl ApiEventMetric for RefundsRetrieveRequest { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Refund { @@ -36,6 +49,7 @@ impl ApiEventMetric for RefundsRetrieveRequest { } } +#[cfg(feature = "v1")] impl ApiEventMetric for RefundUpdateRequest { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Refund { @@ -45,6 +59,7 @@ impl ApiEventMetric for RefundUpdateRequest { } } +#[cfg(feature = "v1")] impl ApiEventMetric for RefundManualUpdateRequest { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Refund { diff --git a/crates/api_models/src/events/routing.rs b/crates/api_models/src/events/routing.rs index ffa5e008f0a5..f3e169336bfa 100644 --- a/crates/api_models/src/events/routing.rs +++ b/crates/api_models/src/events/routing.rs @@ -4,9 +4,9 @@ use crate::routing::{ LinkedRoutingConfigRetrieveResponse, MerchantRoutingAlgorithm, ProfileDefaultRoutingConfig, RoutingAlgorithmId, RoutingConfigRequest, RoutingDictionaryRecord, RoutingKind, RoutingLinkWrapper, RoutingPayloadWrapper, RoutingRetrieveLinkQuery, - RoutingRetrieveLinkQueryWrapper, RoutingRetrieveQuery, SuccessBasedRoutingConfig, - SuccessBasedRoutingPayloadWrapper, SuccessBasedRoutingUpdateConfigQuery, - ToggleSuccessBasedRoutingQuery, ToggleSuccessBasedRoutingWrapper, + RoutingRetrieveLinkQueryWrapper, RoutingRetrieveQuery, RoutingVolumeSplitWrapper, + SuccessBasedRoutingConfig, SuccessBasedRoutingPayloadWrapper, + SuccessBasedRoutingUpdateConfigQuery, ToggleDynamicRoutingQuery, ToggleDynamicRoutingWrapper, }; impl ApiEventMetric for RoutingKind { @@ -79,7 +79,7 @@ impl ApiEventMetric for RoutingRetrieveLinkQueryWrapper { } } -impl ApiEventMetric for ToggleSuccessBasedRoutingQuery { +impl ApiEventMetric for ToggleDynamicRoutingQuery { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Routing) } @@ -97,7 +97,7 @@ impl ApiEventMetric for SuccessBasedRoutingPayloadWrapper { } } -impl ApiEventMetric for ToggleSuccessBasedRoutingWrapper { +impl ApiEventMetric for ToggleDynamicRoutingWrapper { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Routing) } @@ -108,3 +108,9 @@ impl ApiEventMetric for SuccessBasedRoutingUpdateConfigQuery { Some(ApiEventsType::Routing) } } + +impl ApiEventMetric for RoutingVolumeSplitWrapper { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Routing) + } +} diff --git a/crates/api_models/src/events/user.rs b/crates/api_models/src/events/user.rs index 9b100a7321e6..aab8f518abe7 100644 --- a/crates/api_models/src/events/user.rs +++ b/crates/api_models/src/events/user.rs @@ -1,45 +1,28 @@ use common_utils::events::{ApiEventMetric, ApiEventsType}; -#[cfg(feature = "recon")] -use masking::PeekInterface; #[cfg(feature = "dummy_connector")] use crate::user::sample_data::SampleDataRequest; -#[cfg(feature = "recon")] -use crate::user::VerifyTokenResponse; +#[cfg(feature = "control_center_theme")] +use crate::user::theme::{ + CreateThemeRequest, GetThemeResponse, UpdateThemeRequest, UploadFileRequest, +}; use crate::user::{ dashboard_metadata::{ GetMetaDataRequest, GetMetaDataResponse, GetMultipleMetaDataPayload, SetMetaDataRequest, }, AcceptInviteFromEmailRequest, AuthSelectRequest, AuthorizeResponse, BeginTotpResponse, ChangePasswordRequest, ConnectAccountRequest, CreateInternalUserRequest, - CreateUserAuthenticationMethodRequest, DashboardEntryResponse, ForgotPasswordRequest, + CreateTenantUserRequest, CreateUserAuthenticationMethodRequest, ForgotPasswordRequest, GetSsoAuthUrlRequest, GetUserAuthenticationMethodsRequest, GetUserDetailsResponse, - GetUserRoleDetailsRequest, GetUserRoleDetailsResponse, GetUserRoleDetailsResponseV2, - InviteUserRequest, ListUsersResponse, ReInviteUserRequest, RecoveryCodes, ResetPasswordRequest, - RotatePasswordRequest, SendVerifyEmailRequest, SignUpRequest, SignUpWithMerchantIdRequest, - SsoSignInRequest, SwitchMerchantRequest, SwitchOrganizationRequest, SwitchProfileRequest, - TokenResponse, TwoFactorAuthStatusResponse, UpdateUserAccountDetailsRequest, + GetUserRoleDetailsRequest, GetUserRoleDetailsResponseV2, InviteUserRequest, + ReInviteUserRequest, RecoveryCodes, ResetPasswordRequest, RotatePasswordRequest, + SendVerifyEmailRequest, SignUpRequest, SignUpWithMerchantIdRequest, SsoSignInRequest, + SwitchMerchantRequest, SwitchOrganizationRequest, SwitchProfileRequest, TokenResponse, + TwoFactorAuthStatusResponse, TwoFactorStatus, UpdateUserAccountDetailsRequest, UpdateUserAuthenticationMethodRequest, UserFromEmailRequest, UserMerchantCreate, - VerifyEmailRequest, VerifyRecoveryCodeRequest, VerifyTotpRequest, + UserOrgMerchantCreateRequest, VerifyEmailRequest, VerifyRecoveryCodeRequest, VerifyTotpRequest, }; -impl ApiEventMetric for DashboardEntryResponse { - fn get_api_event_type(&self) -> Option { - Some(ApiEventsType::User { - user_id: self.user_id.clone(), - }) - } -} - -#[cfg(feature = "recon")] -impl ApiEventMetric for VerifyTokenResponse { - fn get_api_event_type(&self) -> Option { - Some(ApiEventsType::User { - user_id: self.user_email.peek().to_string(), - }) - } -} - common_utils::impl_api_event_type!( Miscellaneous, ( @@ -54,8 +37,9 @@ common_utils::impl_api_event_type!( SwitchMerchantRequest, SwitchProfileRequest, CreateInternalUserRequest, + CreateTenantUserRequest, + UserOrgMerchantCreateRequest, UserMerchantCreate, - ListUsersResponse, AuthorizeResponse, ConnectAccountRequest, ForgotPasswordRequest, @@ -69,10 +53,10 @@ common_utils::impl_api_event_type!( UpdateUserAccountDetailsRequest, GetUserDetailsResponse, GetUserRoleDetailsRequest, - GetUserRoleDetailsResponse, GetUserRoleDetailsResponseV2, TokenResponse, TwoFactorAuthStatusResponse, + TwoFactorStatus, UserFromEmailRequest, BeginTotpResponse, VerifyRecoveryCodeRequest, @@ -87,5 +71,16 @@ common_utils::impl_api_event_type!( ) ); +#[cfg(feature = "control_center_theme")] +common_utils::impl_api_event_type!( + Miscellaneous, + ( + GetThemeResponse, + UploadFileRequest, + CreateThemeRequest, + UpdateThemeRequest + ) +); + #[cfg(feature = "dummy_connector")] common_utils::impl_api_event_type!(Miscellaneous, (SampleDataRequest)); diff --git a/crates/api_models/src/events/user_role.rs b/crates/api_models/src/events/user_role.rs index eb09abd471ce..e0df36a3349c 100644 --- a/crates/api_models/src/events/user_role.rs +++ b/crates/api_models/src/events/user_role.rs @@ -2,31 +2,29 @@ use common_utils::events::{ApiEventMetric, ApiEventsType}; use crate::user_role::{ role::{ - CreateRoleRequest, GetRoleRequest, ListRolesAtEntityLevelRequest, ListRolesRequest, - ListRolesResponse, RoleInfoResponseNew, RoleInfoWithGroupsResponse, - RoleInfoWithPermissionsResponse, UpdateRoleRequest, + CreateRoleRequest, GetRoleRequest, GroupsAndResources, ListRolesAtEntityLevelRequest, + ListRolesRequest, RoleInfoResponseNew, RoleInfoWithGroupsResponse, RoleInfoWithParents, + UpdateRoleRequest, }, - AcceptInvitationRequest, AuthorizationInfoResponse, DeleteUserRoleRequest, - ListUsersInEntityRequest, MerchantSelectRequest, UpdateUserRoleRequest, + AuthorizationInfoResponse, DeleteUserRoleRequest, ListUsersInEntityRequest, + UpdateUserRoleRequest, }; common_utils::impl_api_event_type!( Miscellaneous, ( - RoleInfoWithPermissionsResponse, GetRoleRequest, AuthorizationInfoResponse, UpdateUserRoleRequest, - MerchantSelectRequest, - AcceptInvitationRequest, DeleteUserRoleRequest, CreateRoleRequest, UpdateRoleRequest, - ListRolesResponse, ListRolesAtEntityLevelRequest, RoleInfoResponseNew, RoleInfoWithGroupsResponse, ListUsersInEntityRequest, - ListRolesRequest + ListRolesRequest, + GroupsAndResources, + RoleInfoWithParents ) ); diff --git a/crates/api_models/src/feature_matrix.rs b/crates/api_models/src/feature_matrix.rs new file mode 100644 index 000000000000..2eaade1b61e8 --- /dev/null +++ b/crates/api_models/src/feature_matrix.rs @@ -0,0 +1,46 @@ +use std::collections::HashSet; + +use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; + +use crate::enums::{ + CaptureMethod, Connector, CountryAlpha2, Currency, EventClass, FeatureStatus, + PaymentConnectorCategory, PaymentMethod, PaymentMethodType, +}; + +#[derive(Default, Debug, Deserialize, Serialize, Clone, ToSchema)] +pub struct FeatureMatrixRequest { + // List of connectors for which the feature matrix is requested + pub connectors: Option>, +} + +#[derive(Debug, ToSchema, Serialize)] +pub struct SupportedPaymentMethod { + pub payment_method: PaymentMethod, + pub payment_method_type: PaymentMethodType, + pub mandates: FeatureStatus, + pub refunds: FeatureStatus, + pub supported_capture_methods: Vec, + pub supported_countries: Option>, + pub supported_currencies: Option>, +} + +#[derive(Debug, ToSchema, Serialize)] +pub struct ConnectorFeatureMatrixResponse { + pub name: String, + pub description: Option, + pub category: Option, + pub supported_payment_methods: Vec, + pub supported_webhook_flows: Option>, +} + +#[derive(Debug, Serialize, ToSchema)] +pub struct FeatureMatrixListResponse { + /// The number of connectors included in the response + pub connector_count: usize, + // The list of payments response objects + pub connectors: Vec, +} + +impl common_utils::events::ApiEventMetric for FeatureMatrixListResponse {} +impl common_utils::events::ApiEventMetric for FeatureMatrixRequest {} diff --git a/crates/api_models/src/gsm.rs b/crates/api_models/src/gsm.rs index 30e49062439f..b95794ef707b 100644 --- a/crates/api_models/src/gsm.rs +++ b/crates/api_models/src/gsm.rs @@ -1,3 +1,4 @@ +use common_enums::ErrorCategory; use utoipa::ToSchema; use crate::enums::Connector; @@ -26,6 +27,8 @@ pub struct GsmCreateRequest { pub unified_code: Option, /// error message unified across the connectors pub unified_message: Option, + /// category in which error belongs to + pub error_category: Option, } #[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema)] @@ -88,6 +91,8 @@ pub struct GsmUpdateRequest { pub unified_code: Option, /// error message unified across the connectors pub unified_message: Option, + /// category in which error belongs to + pub error_category: Option, } #[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema)] @@ -141,4 +146,6 @@ pub struct GsmResponse { pub unified_code: Option, /// error message unified across the connectors pub unified_message: Option, + /// category in which error belongs to + pub error_category: Option, } diff --git a/crates/api_models/src/health_check.rs b/crates/api_models/src/health_check.rs index 1e86e2964c78..4a1c009e43eb 100644 --- a/crates/api_models/src/health_check.rs +++ b/crates/api_models/src/health_check.rs @@ -1,3 +1,4 @@ +use std::collections::hash_map::HashMap; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct RouterHealthCheckResponse { pub database: bool, @@ -9,10 +10,22 @@ pub struct RouterHealthCheckResponse { #[cfg(feature = "olap")] pub opensearch: bool, pub outgoing_request: bool, + #[cfg(feature = "dynamic_routing")] + pub grpc_health_check: HealthCheckMap, } impl common_utils::events::ApiEventMetric for RouterHealthCheckResponse {} +/// gRPC based services eligible for Health check +#[derive(Debug, Clone, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum HealthCheckServices { + /// Dynamic routing service + DynamicRoutingService, +} + +pub type HealthCheckMap = HashMap; + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct SchedulerHealthCheckResponse { pub database: bool, diff --git a/crates/api_models/src/lib.rs b/crates/api_models/src/lib.rs index daa329a76317..60d1d11f27db 100644 --- a/crates/api_models/src/lib.rs +++ b/crates/api_models/src/lib.rs @@ -5,6 +5,7 @@ pub mod apple_pay_certificates_migration; pub mod blocklist; pub mod cards_info; pub mod conditional_configs; +pub mod connector_enums; pub mod connector_onboarding; pub mod consts; pub mod currency; @@ -15,6 +16,7 @@ pub mod ephemeral_key; #[cfg(feature = "errors")] pub mod errors; pub mod events; +pub mod feature_matrix; pub mod files; pub mod gsm; pub mod health_check; @@ -30,6 +32,7 @@ pub mod poll; #[cfg(feature = "recon")] pub mod recon; pub mod refunds; +pub mod relay; pub mod routing; pub mod surcharge_decision_configs; pub mod user; diff --git a/crates/api_models/src/mandates.rs b/crates/api_models/src/mandates.rs index fe5b053b1808..2d61a630988e 100644 --- a/crates/api_models/src/mandates.rs +++ b/crates/api_models/src/mandates.rs @@ -121,6 +121,10 @@ pub enum RecurringDetails { MandateId(String), PaymentMethodId(String), ProcessorPaymentToken(ProcessorPaymentToken), + + /// Network transaction ID and Card Details for MIT payments when payment_method_data + /// is not stored in the application + NetworkTransactionIdAndCardDetails(NetworkTransactionIdAndCardDetails), } /// Processor payment token for MIT payments where payment_method_data is not available @@ -130,3 +134,54 @@ pub struct ProcessorPaymentToken { #[schema(value_type = Option)] pub merchant_connector_id: Option, } + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema, PartialEq, Eq)] +pub struct NetworkTransactionIdAndCardDetails { + /// The card number + #[schema(value_type = String, example = "4242424242424242")] + pub card_number: cards::CardNumber, + + /// The card's expiry month + #[schema(value_type = String, example = "24")] + pub card_exp_month: Secret, + + /// The card's expiry year + #[schema(value_type = String, example = "24")] + pub card_exp_year: Secret, + + /// The card holder's name + #[schema(value_type = String, example = "John Test")] + pub card_holder_name: Option>, + + /// The name of the issuer of card + #[schema(example = "chase")] + pub card_issuer: Option, + + /// The card network for the card + #[schema(value_type = Option, example = "Visa")] + pub card_network: Option, + + #[schema(example = "CREDIT")] + pub card_type: Option, + + #[schema(example = "INDIA")] + pub card_issuing_country: Option, + + #[schema(example = "JP_AMEX")] + pub bank_code: Option, + + /// The card holder's nick name + #[schema(value_type = Option, example = "John Test")] + pub nick_name: Option>, + + /// The network transaction ID provided by the card network during a CIT (Customer Initiated Transaction), + /// where `setup_future_usage` is set to `off_session`. + #[schema(value_type = String)] + pub network_transaction_id: Secret, +} + +impl RecurringDetails { + pub fn is_network_transaction_id_and_card_details_flow(self) -> bool { + matches!(self, Self::NetworkTransactionIdAndCardDetails(_)) + } +} diff --git a/crates/api_models/src/organization.rs b/crates/api_models/src/organization.rs index f95a15951165..c6bc3924d115 100644 --- a/crates/api_models/src/organization.rs +++ b/crates/api_models/src/organization.rs @@ -22,9 +22,14 @@ pub struct OrganizationId { #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)] #[serde(deny_unknown_fields)] pub struct OrganizationCreateRequest { + /// Name of the organization pub organization_name: String, + + /// Details about the organization #[schema(value_type = Option)] pub organization_details: Option, + + /// Metadata is useful for storing additional, unstructured information on an object. #[schema(value_type = Option)] pub metadata: Option, } @@ -32,20 +37,53 @@ pub struct OrganizationCreateRequest { #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)] #[serde(deny_unknown_fields)] pub struct OrganizationUpdateRequest { + /// Name of the organization pub organization_name: Option, + + /// Details about the organization #[schema(value_type = Option)] pub organization_details: Option, + + /// Metadata is useful for storing additional, unstructured information on an object. #[schema(value_type = Option)] pub metadata: Option, } - +#[cfg(feature = "v1")] #[derive(Debug, serde::Serialize, Clone, ToSchema)] pub struct OrganizationResponse { + /// The unique identifier for the Organization #[schema(value_type = String, max_length = 64, min_length = 1, example = "org_q98uSGAYbjEwqs0mJwnz")] pub organization_id: id_type::OrganizationId, + + /// Name of the Organization pub organization_name: Option, + + /// Details about the organization #[schema(value_type = Option)] pub organization_details: Option, + + /// Metadata is useful for storing additional, unstructured information on an object. + #[schema(value_type = Option)] + pub metadata: Option, + pub modified_at: time::PrimitiveDateTime, + pub created_at: time::PrimitiveDateTime, +} + +#[cfg(feature = "v2")] +#[derive(Debug, serde::Serialize, Clone, ToSchema)] +pub struct OrganizationResponse { + /// The unique identifier for the Organization + #[schema(value_type = String, max_length = 64, min_length = 1, example = "org_q98uSGAYbjEwqs0mJwnz")] + pub id: id_type::OrganizationId, + + /// Name of the Organization + pub organization_name: Option, + + /// Details about the organization + #[schema(value_type = Option)] + pub organization_details: Option, + + /// Metadata is useful for storing additional, unstructured information on an object. #[schema(value_type = Option)] pub metadata: Option, pub modified_at: time::PrimitiveDateTime, diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index 1a82dc0e26bd..18d18f08fd24 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -6,6 +6,8 @@ use cards::CardNumber; use common_utils::{ consts::SURCHARGE_PERCENTAGE_PRECISION_LENGTH, crypto::OptionalEncryptableName, + errors, + ext_traits::OptionExt, id_type, link_utils, pii, types::{MinorUnit, Percentage, Surcharge}, }; @@ -108,19 +110,24 @@ pub struct PaymentMethodCreate { pub struct PaymentMethodCreate { /// The type of payment method use for the payment. #[schema(value_type = PaymentMethod,example = "card")] - pub payment_method: api_enums::PaymentMethod, + pub payment_method_type: api_enums::PaymentMethod, /// This is a sub-category of payment method. #[schema(value_type = PaymentMethodType,example = "credit")] - pub payment_method_type: api_enums::PaymentMethodType, + pub payment_method_subtype: api_enums::PaymentMethodType, /// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object. #[schema(value_type = Option,example = json!({ "city": "NY", "unit": "245" }))] pub metadata: Option, /// The unique identifier of the customer. - #[schema(value_type = String, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")] - pub customer_id: id_type::CustomerId, + #[schema( + min_length = 32, + max_length = 64, + example = "12345_cus_01926c58bc6e77c09e809964e72af8c8", + value_type = String + )] + pub customer_id: id_type::GlobalCustomerId, /// Payment method data to be passed pub payment_method_data: PaymentMethodCreateData, @@ -143,17 +150,19 @@ pub struct PaymentMethodIntentCreate { pub billing: Option, /// The unique identifier of the customer. - #[schema(value_type = String, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")] - pub customer_id: id_type::CustomerId, + #[schema( + min_length = 32, + max_length = 64, + example = "12345_cus_01926c58bc6e77c09e809964e72af8c8", + value_type = String + )] + pub customer_id: id_type::GlobalCustomerId, } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] #[serde(deny_unknown_fields)] pub struct PaymentMethodIntentConfirm { - /// For SDK based calls, client_secret would be required - pub client_secret: String, - /// The unique identifier of the customer. #[schema(value_type = Option, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")] pub customer_id: Option, @@ -163,20 +172,20 @@ pub struct PaymentMethodIntentConfirm { /// The type of payment method use for the payment. #[schema(value_type = PaymentMethod,example = "card")] - pub payment_method: api_enums::PaymentMethod, + pub payment_method_type: api_enums::PaymentMethod, /// This is a sub-category of payment method. #[schema(value_type = PaymentMethodType,example = "credit")] - pub payment_method_type: api_enums::PaymentMethodType, + pub payment_method_subtype: api_enums::PaymentMethodType, } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] impl PaymentMethodIntentConfirm { pub fn validate_payment_method_data_against_payment_method( - payment_method: api_enums::PaymentMethod, + payment_method_type: api_enums::PaymentMethod, payment_method_data: PaymentMethodCreateData, ) -> bool { - match payment_method { + match payment_method_type { api_enums::PaymentMethod::Card => { matches!(payment_method_data, PaymentMethodCreateData::Card(_)) } @@ -193,14 +202,11 @@ pub struct PaymentMethodIntentConfirmInternal { pub id: String, /// The type of payment method use for the payment. #[schema(value_type = PaymentMethod,example = "card")] - pub payment_method: api_enums::PaymentMethod, + pub payment_method_type: api_enums::PaymentMethod, /// This is a sub-category of payment method. #[schema(value_type = PaymentMethodType,example = "credit")] - pub payment_method_type: api_enums::PaymentMethodType, - - /// For SDK based calls, client_secret would be required - pub client_secret: String, + pub payment_method_subtype: api_enums::PaymentMethodType, /// The unique identifier of the customer. #[schema(value_type = Option, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")] @@ -214,9 +220,8 @@ pub struct PaymentMethodIntentConfirmInternal { impl From for PaymentMethodIntentConfirm { fn from(item: PaymentMethodIntentConfirmInternal) -> Self { Self { - client_secret: item.client_secret, - payment_method: item.payment_method, payment_method_type: item.payment_method_type, + payment_method_subtype: item.payment_method_subtype, customer_id: item.customer_id, payment_method_data: item.payment_method_data.clone(), } @@ -243,6 +248,9 @@ pub struct PaymentMethodMigrate { /// Card Details pub card: Option, + /// Network token details + pub network_token: Option, + /// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object. pub metadata: Option, @@ -274,6 +282,24 @@ pub struct PaymentMethodMigrate { pub network_transaction_id: Option, } +#[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct PaymentMethodMigrateResponse { + //payment method response when payment method entry is created + pub payment_method_response: PaymentMethodResponse, + + //card data migration status + pub card_migrated: Option, + + //network token data migration status + pub network_token_migrated: Option, + + //connector mandate details migration status + pub connector_mandate_details_migrated: Option, + + //network transaction id migration status + pub network_transaction_id_migrated: Option, +} + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct PaymentsMandateReference( pub HashMap, @@ -337,10 +363,10 @@ impl PaymentMethodCreate { #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] impl PaymentMethodCreate { pub fn validate_payment_method_data_against_payment_method( - payment_method: api_enums::PaymentMethod, + payment_method_type: api_enums::PaymentMethod, payment_method_data: PaymentMethodCreateData, ) -> bool { - match payment_method { + match payment_method_type { api_enums::PaymentMethod::Card => { matches!(payment_method_data, PaymentMethodCreateData::Card(_)) } @@ -375,10 +401,6 @@ pub struct PaymentMethodUpdate { pub struct PaymentMethodUpdate { /// payment method data to be passed pub payment_method_data: PaymentMethodUpdateData, - - /// This is a 15 minute expiry token which shall be used from the client to authenticate and perform sessions from the SDK - #[schema(max_length = 30, min_length = 30, example = "secret_k2uj3he2893eiu2d")] - pub client_secret: Option, } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] @@ -487,6 +509,7 @@ pub struct CardDetail { pub nick_name: Option>, /// Card Issuing Country + #[schema(value_type = CountryAlpha2)] pub card_issuing_country: Option, /// Card's Network @@ -537,6 +560,53 @@ pub struct MigrateCardDetail { pub card_type: Option, } +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +#[serde(deny_unknown_fields)] +pub struct MigrateNetworkTokenData { + /// Network Token Number + #[schema(value_type = String,example = "4111111145551142")] + pub network_token_number: CardNumber, + + /// Network Token Expiry Month + #[schema(value_type = String,example = "10")] + pub network_token_exp_month: masking::Secret, + + /// Network Token Expiry Year + #[schema(value_type = String,example = "25")] + pub network_token_exp_year: masking::Secret, + + /// Card Holder Name + #[schema(value_type = String,example = "John Doe")] + pub card_holder_name: Option>, + + /// Card Holder's Nick Name + #[schema(value_type = Option,example = "John Doe")] + pub nick_name: Option>, + + /// Card Issuing Country + pub card_issuing_country: Option, + + /// Card's Network + #[schema(value_type = Option)] + pub card_network: Option, + + /// Issuer Bank for Card + pub card_issuer: Option, + + /// Card Type + pub card_type: Option, +} + +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +#[serde(deny_unknown_fields)] +pub struct MigrateNetworkTokenDetail { + /// Network token details + pub network_token_data: MigrateNetworkTokenData, + + /// Network token requestor reference id + pub network_token_requestor_ref_id: String, +} + #[cfg(all( any(feature = "v1", feature = "v2"), not(feature = "payment_methods_v2") @@ -684,7 +754,7 @@ pub struct PaymentMethodResponse { #[schema(value_type = Option, example = json!({ "city": "NY", "unit": "245" }))] pub metadata: Option, - /// A timestamp (ISO 8601 code) that determines when the customer was created + /// A timestamp (ISO 8601 code) that determines when the payment method was created #[schema(value_type = Option, example = "2023-01-18T11:04:09.922Z")] #[serde(default, with = "common_utils::custom_serde::iso8601::option")] pub created: Option, @@ -707,12 +777,17 @@ pub struct PaymentMethodResponse { #[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema, Clone)] pub struct PaymentMethodResponse { /// Unique identifier for a merchant - #[schema(example = "merchant_1671528864")] + #[schema(value_type = String, example = "merchant_1671528864")] pub merchant_id: id_type::MerchantId, /// The unique identifier of the customer. - #[schema(value_type = Option, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")] - pub customer_id: id_type::CustomerId, + #[schema( + min_length = 32, + max_length = 64, + example = "12345_cus_01926c58bc6e77c09e809964e72af8c8", + value_type = String + )] + pub customer_id: id_type::GlobalCustomerId, /// The unique identifier of the Payment method #[schema(example = "card_rGK4Vi5iSW70MY7J2mIg")] @@ -720,21 +795,17 @@ pub struct PaymentMethodResponse { /// The type of payment method use for the payment. #[schema(value_type = PaymentMethod, example = "card")] - pub payment_method: Option, + pub payment_method_type: Option, /// This is a sub-category of payment method. #[schema(value_type = Option, example = "credit")] - pub payment_method_type: Option, + pub payment_method_subtype: Option, /// Indicates whether the payment method is eligible for recurring payments #[schema(example = true)] pub recurring_enabled: bool, - /// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object. - #[schema(value_type = Option, example = json!({ "city": "NY", "unit": "245" }))] - pub metadata: Option, - - /// A timestamp (ISO 8601 code) that determines when the customer was created + /// A timestamp (ISO 8601 code) that determines when the payment method was created #[schema(value_type = Option, example = "2023-01-18T11:04:09.922Z")] #[serde(default, with = "common_utils::custom_serde::iso8601::option")] pub created: Option, @@ -744,7 +815,8 @@ pub struct PaymentMethodResponse { pub last_used_at: Option, /// For Client based calls - pub client_secret: Option, + #[schema(value_type=Option)] + pub ephemeral_key: Option>, pub payment_method_data: Option, } @@ -753,7 +825,9 @@ pub struct PaymentMethodResponse { pub enum PaymentMethodsData { Card(CardDetailsPaymentMethod), BankDetails(PaymentMethodDataBankCreds), + WalletDetails(PaymentMethodDataWalletInfo), } + #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub struct CardDetailsPaymentMethod { pub last4_digits: Option, @@ -780,6 +854,37 @@ pub struct PaymentMethodDataBankCreds { pub connector_details: Vec, } +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct PaymentMethodDataWalletInfo { + /// Last 4 digits of the card number + pub last4: String, + /// The information of the payment method + pub card_network: String, + /// The type of payment method + #[serde(rename = "type")] + pub card_type: String, +} + +impl From for PaymentMethodDataWalletInfo { + fn from(item: payments::additional_info::WalletAdditionalDataForCard) -> Self { + Self { + last4: item.last4, + card_network: item.card_network, + card_type: item.card_type, + } + } +} + +impl From for payments::additional_info::WalletAdditionalDataForCard { + fn from(item: PaymentMethodDataWalletInfo) -> Self { + Self { + last4: item.last4, + card_network: item.card_network, + card_type: item.card_type, + } + } +} + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct BankAccountTokenData { pub payment_method_type: api_enums::PaymentMethodType, @@ -854,6 +959,7 @@ pub struct CardDetailFromLocker { #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] pub struct CardDetailFromLocker { + #[schema(value_type = Option)] pub issuer_country: Option, pub last4_digits: Option, #[serde(skip)] @@ -1101,10 +1207,14 @@ pub struct BankDebitTypes { pub eligible_connectors: Vec, } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] #[derive(Debug, Clone, serde::Serialize, ToSchema, PartialEq)] pub struct ResponsePaymentMethodTypes { /// The payment method type enabled - #[schema(example = "klarna")] + #[schema(example = "klarna", value_type = PaymentMethodType)] pub payment_method_type: api_enums::PaymentMethodType, /// The list of payment experiences enabled, if applicable for a payment method type @@ -1118,6 +1228,7 @@ pub struct ResponsePaymentMethodTypes { /// The Bank debit payment method information, if applicable for a payment method type. pub bank_debits: Option, + /// The Bank transfer payment method information, if applicable for a payment method type. pub bank_transfers: Option, @@ -1131,6 +1242,41 @@ pub struct ResponsePaymentMethodTypes { pub pm_auth_connector: Option, } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, Clone, serde::Serialize, ToSchema, PartialEq)] +#[serde(untagged)] // Untagged used for serialization only +pub enum PaymentMethodSubtypeSpecificData { + Card { + card_networks: Vec, + }, + Bank { + bank_names: Vec, + }, +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, Clone, serde::Serialize, ToSchema, PartialEq)] +pub struct ResponsePaymentMethodTypes { + /// The payment method type enabled + #[schema(example = "klarna", value_type = PaymentMethodType)] + pub payment_method_type: common_enums::PaymentMethod, + + /// The payment method subtype enabled + #[schema(example = "klarna", value_type = PaymentMethodType)] + pub payment_method_subtype: common_enums::PaymentMethodType, + + /// payment method subtype specific information + #[serde(flatten)] + pub extra_information: Option, + + /// Required fields for the payment_method_type. + /// This is the union of all the required fields for the payment method type enabled in all the connectors. + pub required_fields: Option>, + + /// surcharge details for this payment method type if exists + pub surcharge_details: Option, +} + #[derive(Clone, Debug, PartialEq, serde::Serialize, ToSchema)] #[serde(rename_all = "snake_case")] pub struct SurchargeDetailsResponse { @@ -1144,8 +1290,6 @@ pub struct SurchargeDetailsResponse { pub display_tax_on_surcharge_amount: f64, /// sum of display_surcharge_amount and display_tax_on_surcharge_amount pub display_total_surcharge_amount: f64, - /// sum of original amount, - pub display_final_amount: f64, } #[derive(Clone, Debug, PartialEq, serde::Serialize, ToSchema)] @@ -1219,12 +1363,14 @@ pub struct ResponsePaymentMethodIntermediate { pub card_networks: Option>, pub payment_method: api_enums::PaymentMethod, pub connector: String, + pub merchant_connector_id: String, } impl ResponsePaymentMethodIntermediate { pub fn new( pm_type: RequestPaymentMethodTypes, connector: String, + merchant_connector_id: String, pm: api_enums::PaymentMethod, ) -> Self { Self { @@ -1233,6 +1379,7 @@ impl ResponsePaymentMethodIntermediate { card_networks: pm_type.card_networks, payment_method: pm, connector, + merchant_connector_id, } } } @@ -1542,18 +1689,6 @@ pub struct PaymentMethodListResponse { pub currency: Option, /// Information about the payment method - #[schema(value_type = Vec,example = json!( - [ - { - "payment_method": "wallet", - "payment_experience": null, - "payment_method_issuers": [ - "labore magna ipsum", - "aute" - ] - } - ] - ))] pub payment_methods: Vec, /// Value indicating if the current payment is a mandate payment #[schema(value_type = MandateType)] @@ -1583,35 +1718,6 @@ pub struct PaymentMethodListResponse { pub is_tax_calculation_enabled: bool, } -#[derive(Eq, PartialEq, Hash, Debug, serde::Deserialize, ToSchema)] -pub struct PaymentMethodList { - /// The type of payment method use for the payment. - #[schema(value_type = PaymentMethod,example = "card")] - pub payment_method: api_enums::PaymentMethod, - - /// This is a sub-category of payment method. - #[schema(value_type = Option>,example = json!(["credit"]))] - pub payment_method_types: Option>, -} - -/// Currently if the payment method is Wallet or Paylater the relevant fields are `payment_method` -/// and `payment_method_issuers`. Otherwise only consider -/// `payment_method`,`payment_method_issuers`,`payment_method_types`,`payment_schemes` fields. -impl serde::Serialize for PaymentMethodList { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - use serde::ser::SerializeStruct; - let mut state = serializer.serialize_struct("PaymentMethodList", 4)?; - state.serialize_field("payment_method", &self.payment_method)?; - - state.serialize_field("payment_method_types", &self.payment_method_types)?; - - state.end() - } -} - #[cfg(all( any(feature = "v2", feature = "v1"), not(feature = "payment_methods_v2") @@ -1682,16 +1788,21 @@ pub struct CustomerPaymentMethod { pub payment_method_id: String, /// The unique identifier of the customer. - #[schema(value_type = String, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")] - pub customer_id: id_type::CustomerId, + #[schema( + min_length = 32, + max_length = 64, + example = "12345_cus_01926c58bc6e77c09e809964e72af8c8", + value_type = String + )] + pub customer_id: id_type::GlobalCustomerId, /// The type of payment method use for the payment. #[schema(value_type = PaymentMethod,example = "card")] - pub payment_method: api_enums::PaymentMethod, + pub payment_method_type: api_enums::PaymentMethod, /// This is a sub-category of payment method. #[schema(value_type = Option,example = "credit_card")] - pub payment_method_type: Option, + pub payment_method_subtype: Option, /// Indicates whether the payment method is eligible for recurring payments #[schema(example = true)] @@ -1704,14 +1815,10 @@ pub struct CustomerPaymentMethod { #[schema(example = json!({"mask": "0000"}))] pub bank: Option, - /// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object. - #[schema(value_type = Option,example = json!({ "city": "NY", "unit": "245" }))] - pub metadata: Option, - - /// A timestamp (ISO 8601 code) that determines when the customer was created - #[schema(value_type = Option,example = "2023-01-18T11:04:09.922Z")] - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] - pub created: Option, + /// A timestamp (ISO 8601 code) that determines when the payment method was created + #[schema(value_type = PrimitiveDateTime, example = "2023-01-18T11:04:09.922Z")] + #[serde(with = "common_utils::custom_serde::iso8601")] + pub created: time::PrimitiveDateTime, /// Surcharge details for this saved card pub surcharge_details: Option, @@ -1739,6 +1846,7 @@ pub struct CustomerPaymentMethod { pub enum PaymentMethodListData { Card(CardDetailFromLocker), #[cfg(feature = "payouts")] + #[schema(value_type = Bank)] Bank(payouts::Bank), } @@ -1795,7 +1903,7 @@ pub struct CustomerPaymentMethod { #[schema(value_type = Option,example = json!({ "city": "NY", "unit": "245" }))] pub metadata: Option, - /// A timestamp (ISO 8601 code) that determines when the customer was created + /// A timestamp (ISO 8601 code) that determines when the payment method was created #[schema(value_type = Option,example = "2023-01-18T11:04:09.922Z")] #[serde(default, with = "common_utils::custom_serde::iso8601::option")] pub created: Option, @@ -2064,16 +2172,16 @@ pub struct PaymentMethodRecord { pub email: Option, pub phone: Option>, pub phone_country_code: Option, - pub merchant_id: id_type::MerchantId, + pub merchant_id: Option, pub payment_method: Option, pub payment_method_type: Option, pub nick_name: masking::Secret, - pub payment_instrument_id: masking::Secret, + pub payment_instrument_id: Option>, pub card_number_masked: masking::Secret, pub card_expiry_month: masking::Secret, pub card_expiry_year: masking::Secret, pub card_scheme: Option, - pub original_transaction_id: String, + pub original_transaction_id: Option, pub billing_address_zip: masking::Secret, pub billing_address_state: masking::Secret, pub billing_address_first_name: masking::Secret, @@ -2084,10 +2192,14 @@ pub struct PaymentMethodRecord { pub billing_address_line2: Option>, pub billing_address_line3: Option>, pub raw_card_number: Option>, - pub merchant_connector_id: id_type::MerchantConnectorAccountId, + pub merchant_connector_id: Option, pub original_transaction_amount: Option, pub original_transaction_currency: Option, pub line_number: Option, + pub network_token_number: Option, + pub network_token_expiry_month: Option>, + pub network_token_expiry_year: Option>, + pub network_token_requestor_ref_id: Option, } #[derive(Debug, Default, serde::Serialize)] @@ -2104,6 +2216,10 @@ pub struct PaymentMethodMigrationResponse { #[serde(skip_serializing_if = "Option::is_none")] pub migration_error: Option, pub card_number_masked: Option>, + pub card_migrated: Option, + pub network_token_migrated: Option, + pub connector_mandate_details_migrated: Option, + pub network_transaction_id_migrated: Option, } #[derive(Debug, Default, serde::Serialize)] @@ -2113,8 +2229,10 @@ pub enum MigrationStatus { Failed, } -type PaymentMethodMigrationResponseType = - (Result, PaymentMethodRecord); +type PaymentMethodMigrationResponseType = ( + Result, + PaymentMethodRecord, +); #[cfg(all( any(feature = "v2", feature = "v1"), not(feature = "payment_methods_v2") @@ -2123,14 +2241,18 @@ impl From for PaymentMethodMigrationResponse fn from((response, record): PaymentMethodMigrationResponseType) -> Self { match response { Ok(res) => Self { - payment_method_id: Some(res.payment_method_id), - payment_method: res.payment_method, - payment_method_type: res.payment_method_type, - customer_id: res.customer_id, + payment_method_id: Some(res.payment_method_response.payment_method_id), + payment_method: res.payment_method_response.payment_method, + payment_method_type: res.payment_method_response.payment_method_type, + customer_id: res.payment_method_response.customer_id, migration_status: MigrationStatus::Success, migration_error: None, card_number_masked: Some(record.card_number_masked), line_number: record.line_number, + card_migrated: res.card_migrated, + network_token_migrated: res.network_token_migrated, + connector_mandate_details_migrated: res.connector_mandate_details_migrated, + network_transaction_id_migrated: res.network_transaction_id_migrated, }, Err(e) => Self { customer_id: Some(record.customer_id), @@ -2144,57 +2266,70 @@ impl From for PaymentMethodMigrationResponse } } -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -impl From for PaymentMethodMigrationResponse { - fn from((response, record): PaymentMethodMigrationResponseType) -> Self { - match response { - Ok(res) => Self { - payment_method_id: Some(res.payment_method_id), - payment_method: res.payment_method, - payment_method_type: res.payment_method_type, - customer_id: Some(res.customer_id), - migration_status: MigrationStatus::Success, - migration_error: None, - card_number_masked: Some(record.card_number_masked), - line_number: record.line_number, - }, - Err(e) => Self { - customer_id: Some(record.customer_id), - migration_status: MigrationStatus::Failed, - migration_error: Some(e), - card_number_masked: Some(record.card_number_masked), - line_number: record.line_number, - ..Self::default() - }, - } - } -} - -impl From for PaymentMethodMigrate { - fn from(record: PaymentMethodRecord) -> Self { - let mut mandate_reference = HashMap::new(); - mandate_reference.insert( - record.merchant_connector_id, - PaymentsMandateReferenceRecord { - connector_mandate_id: record.payment_instrument_id.peek().to_string(), - payment_method_type: record.payment_method_type, - original_payment_authorized_amount: record.original_transaction_amount, - original_payment_authorized_currency: record.original_transaction_currency, - }, - ); - Self { - merchant_id: record.merchant_id, +impl + TryFrom<( + PaymentMethodRecord, + id_type::MerchantId, + Option, + )> for PaymentMethodMigrate +{ + type Error = error_stack::Report; + fn try_from( + item: ( + PaymentMethodRecord, + id_type::MerchantId, + Option, + ), + ) -> Result { + let (record, merchant_id, mca_id) = item; + + // if payment instrument id is present then only construct this + let connector_mandate_details = if record.payment_instrument_id.is_some() { + Some(PaymentsMandateReference(HashMap::from([( + mca_id.get_required_value("merchant_connector_id")?, + PaymentsMandateReferenceRecord { + connector_mandate_id: record + .payment_instrument_id + .get_required_value("payment_instrument_id")? + .peek() + .to_string(), + payment_method_type: record.payment_method_type, + original_payment_authorized_amount: record.original_transaction_amount, + original_payment_authorized_currency: record.original_transaction_currency, + }, + )]))) + } else { + None + }; + Ok(Self { + merchant_id, customer_id: Some(record.customer_id), card: Some(MigrateCardDetail { card_number: record.raw_card_number.unwrap_or(record.card_number_masked), card_exp_month: record.card_expiry_month, card_exp_year: record.card_expiry_year, - card_holder_name: record.name, + card_holder_name: record.name.clone(), card_network: None, card_type: None, card_issuer: None, card_issuing_country: None, - nick_name: Some(record.nick_name), + nick_name: Some(record.nick_name.clone()), + }), + network_token: Some(MigrateNetworkTokenDetail { + network_token_data: MigrateNetworkTokenData { + network_token_number: record.network_token_number.unwrap_or_default(), + network_token_exp_month: record.network_token_expiry_month.unwrap_or_default(), + network_token_exp_year: record.network_token_expiry_year.unwrap_or_default(), + card_holder_name: record.name, + nick_name: Some(record.nick_name.clone()), + card_issuing_country: None, + card_network: None, + card_issuer: None, + card_type: None, + }, + network_token_requestor_ref_id: record + .network_token_requestor_ref_id + .unwrap_or_default(), }), payment_method: record.payment_method, payment_method_type: record.payment_method_type, @@ -2217,7 +2352,7 @@ impl From for PaymentMethodMigrate { }), email: record.email, }), - connector_mandate_details: Some(PaymentsMandateReference(mandate_reference)), + connector_mandate_details, metadata: None, payment_method_issuer_code: None, card_network: None, @@ -2226,17 +2361,18 @@ impl From for PaymentMethodMigrate { #[cfg(feature = "payouts")] wallet: None, payment_method_data: None, - network_transaction_id: record.original_transaction_id.into(), - } + network_transaction_id: record.original_transaction_id, + }) } } #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] -impl From for customers::CustomerRequest { - fn from(record: PaymentMethodRecord) -> Self { +impl From<(PaymentMethodRecord, id_type::MerchantId)> for customers::CustomerRequest { + fn from(value: (PaymentMethodRecord, id_type::MerchantId)) -> Self { + let (record, merchant_id) = value; Self { customer_id: Some(record.customer_id), - merchant_id: record.merchant_id, + merchant_id, name: record.name, email: record.email, phone: record.phone, diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 9d606dacb9ff..a17189440954 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -5,25 +5,30 @@ use std::{ }; pub mod additional_info; use cards::CardNumber; +use common_enums::ProductType; +#[cfg(feature = "v2")] +use common_utils::id_type::GlobalPaymentId; use common_utils::{ consts::default_payments_list_limit, crypto, + errors::ValidationError, ext_traits::{ConfigExt, Encode, ValueExt}, hashing::HashedString, id_type, - pii::{self, Email, EmailStrategy}, - types::{keymanager::ToEncryptable, MinorUnit, StringMajorUnit}, + pii::{self, Email}, + types::{MinorUnit, StringMajorUnit}, }; use error_stack::ResultExt; -use masking::{ExposeInterface, PeekInterface, Secret, SwitchStrategy, WithType}; +use masking::{PeekInterface, Secret, WithType}; use router_derive::Setter; -use rustc_hash::FxHashMap; use serde::{de, ser::Serializer, Deserialize, Deserializer, Serialize}; use strum::Display; use time::{Date, PrimitiveDateTime}; use url::Url; use utoipa::ToSchema; +#[cfg(feature = "v2")] +use crate::payment_methods; use crate::{ admin::{self, MerchantConnectorInfo}, disputes, enums as api_enums, @@ -60,6 +65,7 @@ pub struct ConnectorCode { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema, PartialEq, Eq)] pub struct BankCodeResponse { + #[schema(value_type = Vec)] pub bank_name: Vec, pub eligible_connectors: Vec, } @@ -114,7 +120,8 @@ pub struct CustomerDetailsResponse { pub phone_country_code: Option, } -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +// Serialize is required because the api event requires Serialize to be implemented +#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)] #[serde(deny_unknown_fields)] #[cfg(feature = "v2")] pub struct PaymentsCreateIntentRequest { @@ -148,11 +155,16 @@ pub struct PaymentsCreateIntentRequest { pub shipping: Option
, /// The identifier for the customer - #[schema(value_type = Option, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")] - pub customer_id: Option, + #[schema( + min_length = 32, + max_length = 64, + example = "12345_cus_01926c58bc6e77c09e809964e72af8c8", + value_type = String + )] + pub customer_id: Option, - /// Set to true to indicate that the customer is in your checkout flow during this payment, and therefore is able to authenticate. This parameter should be false when merchant's doing merchant initiated payments and customer is not present while doing the payment. - #[schema(example = true, value_type = Option)] + /// Set to `present` to indicate that the customer is in your checkout flow during this payment, and therefore is able to authenticate. This parameter should be `absent` when merchant's doing merchant initiated payments and customer is not present while doing the payment. + #[schema(example = "present", value_type = Option)] pub customer_present: Option, /// A description for the payment @@ -161,7 +173,7 @@ pub struct PaymentsCreateIntentRequest { /// The URL to which you want the user to be redirected after the completion of the payment operation #[schema(value_type = Option, example = "https://hyperswitch.io")] - pub return_url: Option, + pub return_url: Option, #[schema(value_type = Option, example = "off_session")] pub setup_future_usage: Option, @@ -202,8 +214,8 @@ pub struct PaymentsCreateIntentRequest { pub payment_link_enabled: Option, /// Configure a custom payment link for the particular payment - #[schema(value_type = Option)] - pub payment_link_config: Option, + #[schema(value_type = Option)] + pub payment_link_config: Option, ///Request an incremental authorization, i.e., increase the authorized amount on a confirmed payment before you capture it. #[schema(value_type = Option)] @@ -224,16 +236,189 @@ pub struct PaymentsCreateIntentRequest { Option, } -#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +#[cfg(feature = "v2")] +impl PaymentsCreateIntentRequest { + pub fn get_feature_metadata_as_value( + &self, + ) -> common_utils::errors::CustomResult< + Option, + common_utils::errors::ParsingError, + > { + Ok(self + .feature_metadata + .as_ref() + .map(Encode::encode_to_value) + .transpose()? + .map(Secret::new)) + } + + pub fn get_connector_metadata_as_value( + &self, + ) -> common_utils::errors::CustomResult< + Option, + common_utils::errors::ParsingError, + > { + Ok(self + .connector_metadata + .as_ref() + .map(Encode::encode_to_value) + .transpose()? + .map(Secret::new)) + } + + pub fn get_allowed_payment_method_types_as_value( + &self, + ) -> common_utils::errors::CustomResult< + Option, + common_utils::errors::ParsingError, + > { + Ok(self + .allowed_payment_method_types + .as_ref() + .map(Encode::encode_to_value) + .transpose()? + .map(Secret::new)) + } + + pub fn get_order_details_as_value( + &self, + ) -> common_utils::errors::CustomResult< + Option>, + common_utils::errors::ParsingError, + > { + self.order_details + .as_ref() + .map(|od| { + od.iter() + .map(|order| order.encode_to_value().map(Secret::new)) + .collect::, _>>() + }) + .transpose() + } +} + +// This struct is only used internally, not visible in API Reference +#[derive(Debug, Clone, serde::Serialize)] +#[cfg(feature = "v2")] +pub struct PaymentsGetIntentRequest { + pub id: id_type::GlobalPaymentId, +} + +#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)] +#[serde(deny_unknown_fields)] +#[cfg(feature = "v2")] +pub struct PaymentsUpdateIntentRequest { + pub amount_details: Option, + + /// The routing algorithm id to be used for the payment + #[schema(value_type = Option)] + pub routing_algorithm_id: Option, + + #[schema(value_type = Option, example = "automatic")] + pub capture_method: Option, + + #[schema(value_type = Option, example = "no_three_ds", default = "no_three_ds")] + pub authentication_type: Option, + + /// The billing details of the payment. This address will be used for invoicing. + pub billing: Option
, + + /// The shipping address for the payment + pub shipping: Option
, + + /// Set to `present` to indicate that the customer is in your checkout flow during this payment, and therefore is able to authenticate. This parameter should be `absent` when merchant's doing merchant initiated payments and customer is not present while doing the payment. + #[schema(example = "present", value_type = Option)] + pub customer_present: Option, + + /// A description for the payment + #[schema(example = "It's my first payment request", value_type = Option)] + pub description: Option, + + /// The URL to which you want the user to be redirected after the completion of the payment operation + #[schema(value_type = Option, example = "https://hyperswitch.io")] + pub return_url: Option, + + #[schema(value_type = Option, example = "off_session")] + pub setup_future_usage: Option, + + /// Apply MIT exemption for a payment + #[schema(value_type = Option)] + pub apply_mit_exemption: Option, + + /// For non-card charges, you can use this value as the complete description that appears on your customers’ statements. Must contain at least one letter, maximum 22 characters. + #[schema(max_length = 22, example = "Hyperswitch Router", value_type = Option)] + pub statement_descriptor: Option, + + /// Use this object to capture the details about the different products for which the payment is being made. The sum of amount across different products here should be equal to the overall payment amount + #[schema(value_type = Option>, example = r#"[{ + "product_name": "Apple iPhone 16", + "quantity": 1, + "amount" : 69000 + "product_img_link" : "https://dummy-img-link.com" + }]"#)] + pub order_details: Option>, + + /// Use this parameter to restrict the Payment Method Types to show for a given PaymentIntent + #[schema(value_type = Option>)] + pub allowed_payment_method_types: Option>, + + /// Metadata is useful for storing additional, unstructured information on an object. This metadata will override the metadata that was passed in payments + #[schema(value_type = Option, example = r#"{ "udf1": "some-value", "udf2": "some-value" }"#)] + pub metadata: Option, + + /// Some connectors like Apple pay, Airwallex and Noon might require some additional information, find specific details in the child attributes below. + #[schema(value_type = Option)] + pub connector_metadata: Option, + + /// Additional data that might be required by hyperswitch based on the requested features by the merchants. + #[schema(value_type = Option)] + pub feature_metadata: Option, + + /// Configure a custom payment link for the particular payment + #[schema(value_type = Option)] + pub payment_link_config: Option, + + /// Request an incremental authorization, i.e., increase the authorized amount on a confirmed payment before you capture it. + #[schema(value_type = Option)] + pub request_incremental_authorization: Option, + + /// Will be used to expire client secret after certain amount of time to be supplied in seconds, if not sent it will be taken from profile config + ///(900) for 15 mins + #[schema(value_type = Option, example = 900)] + pub session_expiry: Option, + + /// Additional data related to some frm(Fraud Risk Management) connectors + #[schema(value_type = Option, example = r#"{ "coverage_request" : "fraud", "fulfillment_method" : "delivery" }"#)] + pub frm_metadata: Option, + + /// Whether to perform external authentication (if applicable) + #[schema(value_type = Option)] + pub request_external_three_ds_authentication: + Option, +} + +#[derive(Debug, serde::Serialize, Clone, ToSchema)] #[serde(deny_unknown_fields)] #[cfg(feature = "v2")] -pub struct PaymentsCreateIntentResponse { +pub struct PaymentsIntentResponse { + /// Global Payment Id for the payment + #[schema(value_type = String)] + pub id: id_type::GlobalPaymentId, + + /// The status of the payment + #[schema(value_type = IntentStatus, example = "succeeded")] + pub status: common_enums::IntentStatus, + /// The amount details for the payment - pub amount_details: AmountDetails, + pub amount_details: AmountDetailsResponse, /// It's a token used for client side verification. #[schema(value_type = String, example = "pay_U42c409qyHwOkWo3vK60_secret_el9ksDkiB8hi6j9N78yo")] - pub client_secret: Secret, + pub client_secret: common_utils::types::ClientSecret, + + /// The identifier for the profile. This is inferred from the `x-profile-id` header + #[schema(value_type = String)] + pub profile_id: id_type::ProfileId, /// Unique identifier for the payment. This ensures idempotency for multiple payments /// that have been done by a single merchant. @@ -256,17 +441,24 @@ pub struct PaymentsCreateIntentResponse { pub authentication_type: api_enums::AuthenticationType, /// The billing details of the payment. This address will be used for invoicing. + #[schema(value_type = Option
)] pub billing: Option
, /// The shipping address for the payment + #[schema(value_type = Option
)] pub shipping: Option
, /// The identifier for the customer - #[schema(value_type = Option, max_length = 64, min_length = 1, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")] - pub customer_id: Option, + #[schema( + min_length = 32, + max_length = 64, + example = "12345_cus_01926c58bc6e77c09e809964e72af8c8", + value_type = String + )] + pub customer_id: Option, - /// Set to true to indicate that the customer is in your checkout flow during this payment, and therefore is able to authenticate. This parameter should be false when merchant's doing merchant initiated payments and customer is not present while doing the payment. - #[schema(example = true, value_type = PresenceOfCustomerDuringPayment)] + /// Set to `present` to indicate that the customer is in your checkout flow during this payment, and therefore is able to authenticate. This parameter should be `absent` when merchant's doing merchant initiated payments and customer is not present while doing the payment. + #[schema(example = "present", value_type = PresenceOfCustomerDuringPayment)] pub customer_present: common_enums::PresenceOfCustomerDuringPayment, /// A description for the payment @@ -275,7 +467,7 @@ pub struct PaymentsCreateIntentResponse { /// The URL to which you want the user to be redirected after the completion of the payment operation #[schema(value_type = Option, example = "https://hyperswitch.io")] - pub return_url: Option, + pub return_url: Option, #[schema(value_type = FutureUsage, example = "off_session")] pub setup_future_usage: api_enums::FutureUsage, @@ -306,9 +498,11 @@ pub struct PaymentsCreateIntentResponse { pub metadata: Option, /// Some connectors like Apple pay, Airwallex and Noon might require some additional information, find specific details in the child attributes below. - pub connector_metadata: Option, + #[schema(value_type = Option)] + pub connector_metadata: Option, /// Additional data that might be required by hyperswitch based on the requested features by the merchants. + #[schema(value_type = Option)] pub feature_metadata: Option, /// Whether to generate the payment link for this payment or not (if applicable) @@ -316,17 +510,16 @@ pub struct PaymentsCreateIntentResponse { pub payment_link_enabled: common_enums::EnablePaymentLinkRequest, /// Configure a custom payment link for the particular payment - #[schema(value_type = Option)] - pub payment_link_config: Option, + #[schema(value_type = Option)] + pub payment_link_config: Option, ///Request an incremental authorization, i.e., increase the authorized amount on a confirmed payment before you capture it. #[schema(value_type = RequestIncrementalAuthorization)] pub request_incremental_authorization: common_enums::RequestIncrementalAuthorization, - ///Will be used to expire client secret after certain amount of time to be supplied in seconds, if not sent it will be taken from profile config - ///(900) for 15 mins - #[schema(example = 900)] - pub session_expiry: Option, + ///Will be used to expire client secret after certain amount of time to be supplied in seconds + #[serde(with = "common_utils::custom_serde::iso8601")] + pub expires_on: PrimitiveDateTime, /// Additional data related to some frm(Fraud Risk Management) connectors #[schema(value_type = Option, example = r#"{ "coverage_request" : "fraud", "fulfillment_method" : "delivery" }"#)] @@ -365,6 +558,172 @@ pub struct AmountDetails { tax_on_surcharge: Option, } +#[cfg(feature = "v2")] +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct AmountDetailsUpdate { + /// The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies) + #[schema(value_type = Option, example = 6540)] + #[serde(default, deserialize_with = "amount::deserialize_option")] + order_amount: Option, + /// The currency of the order + #[schema(example = "USD", value_type = Option)] + currency: Option, + /// The shipping cost of the order. This has to be collected from the merchant + shipping_cost: Option, + /// Tax amount related to the order. This will be calculated by the external tax provider + order_tax_amount: Option, + /// The action to whether calculate tax by calling external tax provider or not + #[schema(value_type = Option)] + skip_external_tax_calculation: Option, + /// The action to whether calculate surcharge or not + #[schema(value_type = Option)] + skip_surcharge_calculation: Option, + /// The surcharge amount to be added to the order, collected from the merchant + surcharge_amount: Option, + /// tax on surcharge amount + tax_on_surcharge: Option, +} + +#[cfg(feature = "v2")] +pub struct AmountDetailsSetter { + pub order_amount: Amount, + pub currency: common_enums::Currency, + pub shipping_cost: Option, + pub order_tax_amount: Option, + pub skip_external_tax_calculation: common_enums::TaxCalculationOverride, + pub skip_surcharge_calculation: common_enums::SurchargeCalculationOverride, + pub surcharge_amount: Option, + pub tax_on_surcharge: Option, +} + +#[cfg(feature = "v2")] +#[derive(Clone, Debug, PartialEq, serde::Serialize, ToSchema)] +pub struct AmountDetailsResponse { + /// The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies) + #[schema(value_type = u64, example = 6540)] + pub order_amount: MinorUnit, + /// The currency of the order + #[schema(example = "USD", value_type = Currency)] + pub currency: common_enums::Currency, + /// The shipping cost of the order. This has to be collected from the merchant + pub shipping_cost: Option, + /// Tax amount related to the order. This will be calculated by the external tax provider + pub order_tax_amount: Option, + /// The action to whether calculate tax by calling external tax provider or not + #[schema(value_type = TaxCalculationOverride)] + pub external_tax_calculation: common_enums::TaxCalculationOverride, + /// The action to whether calculate surcharge or not + #[schema(value_type = SurchargeCalculationOverride)] + pub surcharge_calculation: common_enums::SurchargeCalculationOverride, + /// The surcharge amount to be added to the order, collected from the merchant + pub surcharge_amount: Option, + /// tax on surcharge amount + pub tax_on_surcharge: Option, +} + +#[cfg(feature = "v2")] +#[derive(Clone, Debug, PartialEq, serde::Serialize, ToSchema)] +pub struct PaymentAmountDetailsResponse { + /// The payment amount. Amount for the payment in the lowest denomination of the currency, (i.e) in cents for USD denomination, in yen for JPY denomination etc. E.g., Pass 100 to charge $1.00 and 1 for 1¥ since ¥ is a zero-decimal currency. Read more about [the Decimal and Non-Decimal Currencies](https://github.com/juspay/hyperswitch/wiki/Decimal-and-Non%E2%80%90Decimal-Currencies) + #[schema(value_type = u64, example = 6540)] + #[serde(default, deserialize_with = "amount::deserialize")] + pub order_amount: MinorUnit, + /// The currency of the order + #[schema(example = "USD", value_type = Currency)] + pub currency: common_enums::Currency, + /// The shipping cost of the order. This has to be collected from the merchant + pub shipping_cost: Option, + /// Tax amount related to the order. This will be calculated by the external tax provider + pub order_tax_amount: Option, + /// The action to whether calculate tax by calling external tax provider or not + #[schema(value_type = TaxCalculationOverride)] + pub external_tax_calculation: common_enums::TaxCalculationOverride, + /// The action to whether calculate surcharge or not + #[schema(value_type = SurchargeCalculationOverride)] + pub surcharge_calculation: common_enums::SurchargeCalculationOverride, + /// The surcharge amount to be added to the order, collected from the merchant + pub surcharge_amount: Option, + /// tax on surcharge amount + pub tax_on_surcharge: Option, + /// The total amount of the order including tax, surcharge and shipping cost + pub net_amount: MinorUnit, + /// The amount that was requested to be captured for this payment + pub amount_to_capture: Option, + /// The amount that can be captured on the payment. Either in one go or through multiple captures. + /// This is applicable in case the capture method was either `manual` or `manual_multiple` + pub amount_capturable: MinorUnit, + /// The amount that was captured for this payment. This is the sum of all the captures done on this payment + pub amount_captured: Option, +} + +#[cfg(feature = "v2")] +impl AmountDetails { + pub fn new(amount_details_setter: AmountDetailsSetter) -> Self { + Self { + order_amount: amount_details_setter.order_amount, + currency: amount_details_setter.currency, + shipping_cost: amount_details_setter.shipping_cost, + order_tax_amount: amount_details_setter.order_tax_amount, + skip_external_tax_calculation: amount_details_setter.skip_external_tax_calculation, + skip_surcharge_calculation: amount_details_setter.skip_surcharge_calculation, + surcharge_amount: amount_details_setter.surcharge_amount, + tax_on_surcharge: amount_details_setter.tax_on_surcharge, + } + } + pub fn order_amount(&self) -> Amount { + self.order_amount + } + pub fn currency(&self) -> common_enums::Currency { + self.currency + } + pub fn shipping_cost(&self) -> Option { + self.shipping_cost + } + pub fn order_tax_amount(&self) -> Option { + self.order_tax_amount + } + pub fn skip_external_tax_calculation(&self) -> common_enums::TaxCalculationOverride { + self.skip_external_tax_calculation + } + pub fn skip_surcharge_calculation(&self) -> common_enums::SurchargeCalculationOverride { + self.skip_surcharge_calculation + } + pub fn surcharge_amount(&self) -> Option { + self.surcharge_amount + } + pub fn tax_on_surcharge(&self) -> Option { + self.tax_on_surcharge + } +} + +#[cfg(feature = "v2")] +impl AmountDetailsUpdate { + pub fn order_amount(&self) -> Option { + self.order_amount + } + pub fn currency(&self) -> Option { + self.currency + } + pub fn shipping_cost(&self) -> Option { + self.shipping_cost + } + pub fn order_tax_amount(&self) -> Option { + self.order_tax_amount + } + pub fn skip_external_tax_calculation(&self) -> Option { + self.skip_external_tax_calculation + } + pub fn skip_surcharge_calculation(&self) -> Option { + self.skip_surcharge_calculation + } + pub fn surcharge_amount(&self) -> Option { + self.surcharge_amount + } + pub fn tax_on_surcharge(&self) -> Option { + self.tax_on_surcharge + } +} +#[cfg(feature = "v1")] #[derive( Default, Debug, @@ -384,6 +743,10 @@ pub struct PaymentsRequest { // Makes the field mandatory in PaymentsCreateRequest pub amount: Option, + /// Total tax amount applicable to the order + #[schema(value_type = Option, example = 6540)] + pub order_tax_amount: Option, + /// The three letter ISO currency code in uppercase. Eg: 'USD' to charge US Dollars #[schema(example = "USD", value_type = Option)] #[mandatory_in(PaymentsCreateRequest = Currency)] @@ -648,7 +1011,8 @@ pub struct PaymentsRequest { pub recurring_details: Option, /// Fee information to be charged on the payment being collected - pub charges: Option, + #[schema(value_type = Option)] + pub split_payments: Option, /// Merchant's identifier for the payment/invoice. This will be sent to the connector /// if the connector provides support to accept multiple reference ids. @@ -662,8 +1026,29 @@ pub struct PaymentsRequest { /// Whether to calculate tax for this payment intent pub skip_external_tax_calculation: Option, + + /// Choose what kind of sca exemption is required for this payment + #[schema(value_type = Option)] + pub psd2_sca_exemption_type: Option, + + /// Service details for click to pay external authentication + #[schema(value_type = Option)] + pub ctp_service_details: Option, } +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct CtpServiceDetails { + /// merchant transaction id + pub merchant_transaction_id: Option, + /// network transaction correlation id + pub correlation_id: Option, + /// session transaction flow id + pub x_src_flow_id: Option, + /// provider Eg: Visa, Mastercard + pub provider: Option, +} + +#[cfg(feature = "v1")] /// Checks if the inner values of two options are equal /// Returns true if values are not equal, returns false in other cases fn are_optional_values_invalid( @@ -676,6 +1061,7 @@ fn are_optional_values_invalid( } } +#[cfg(feature = "v1")] impl PaymentsRequest { /// Get the customer id /// @@ -727,8 +1113,61 @@ impl PaymentsRequest { None } } + + pub fn get_feature_metadata_as_value( + &self, + ) -> common_utils::errors::CustomResult< + Option, + common_utils::errors::ParsingError, + > { + self.feature_metadata + .as_ref() + .map(Encode::encode_to_value) + .transpose() + } + + pub fn get_connector_metadata_as_value( + &self, + ) -> common_utils::errors::CustomResult< + Option, + common_utils::errors::ParsingError, + > { + self.connector_metadata + .as_ref() + .map(Encode::encode_to_value) + .transpose() + } + + pub fn get_allowed_payment_method_types_as_value( + &self, + ) -> common_utils::errors::CustomResult< + Option, + common_utils::errors::ParsingError, + > { + self.allowed_payment_method_types + .as_ref() + .map(Encode::encode_to_value) + .transpose() + } + + pub fn get_order_details_as_value( + &self, + ) -> common_utils::errors::CustomResult< + Option>, + common_utils::errors::ParsingError, + > { + self.order_details + .as_ref() + .map(|od| { + od.iter() + .map(|order| order.encode_to_value().map(Secret::new)) + .collect::, _>>() + }) + .transpose() + } } +#[cfg(feature = "v1")] #[cfg(test)] mod payments_request_test { use common_utils::generate_customer_id_of_default_length; @@ -797,33 +1236,6 @@ mod payments_request_test { } } -/// Fee information to be charged on the payment being collected -#[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 - #[schema(value_type = i64, example = 6540)] - pub fees: MinorUnit, - - /// 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 { - let surcharge_amount = self - .surcharge_details - .map(|surcharge_details| surcharge_details.get_total_surcharge_amount()) - .unwrap_or_default(); - self.amount - .map(|amount| MinorUnit::from(amount) + surcharge_amount) - } -} - /// Details of surcharge applied on this payment, if applicable #[derive( Default, Debug, Clone, serde::Serialize, serde::Deserialize, Copy, ToSchema, PartialEq, @@ -834,8 +1246,10 @@ pub struct RequestSurchargeDetails { pub tax_amount: Option, } +// for v2 use the type from common_utils::types +#[cfg(feature = "v1")] /// Browser information to be used for 3DS 2.0 -#[derive(ToSchema)] +#[derive(ToSchema, Debug, serde::Deserialize, serde::Serialize)] pub struct BrowserInformation { /// Color depth supported by the browser pub color_depth: Option, @@ -870,6 +1284,15 @@ pub struct BrowserInformation { /// User-agent of the browser pub user_agent: Option, + + /// The os type of the client device + pub os_type: Option, + + /// The os version of the client device + pub os_version: Option, + + /// The device model of the client + pub device_model: Option, } impl RequestSurchargeDetails { @@ -880,28 +1303,13 @@ impl RequestSurchargeDetails { pub fn get_total_surcharge_amount(&self) -> MinorUnit { self.surcharge_amount + self.tax_amount.unwrap_or_default() } -} -#[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, - pub browser_name: Option, - pub x_client_platform: Option, - pub x_merchant_domain: Option, - pub locale: Option, - pub x_app_id: Option, - pub x_redirect_uri: Option, -} + pub fn get_surcharge_amount(&self) -> MinorUnit { + self.surcharge_amount + } -impl HeaderPayload { - pub fn with_source(payment_confirm_source: api_enums::PaymentSource) -> Self { - Self { - payment_confirm_source: Some(payment_confirm_source), - ..Default::default() - } + pub fn get_tax_amount(&self) -> Option { + self.tax_amount } } @@ -915,6 +1323,9 @@ pub struct PaymentAttemptResponse { /// 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., #[schema(value_type = i64, example = 6540)] pub amount: MinorUnit, + /// The payment attempt tax_amount. + #[schema(value_type = Option, example = 6540)] + pub order_tax_amount: Option, /// The currency of the amount of the payment attempt #[schema(value_type = Option, example = "USD")] pub currency: Option, @@ -1003,60 +1414,6 @@ pub struct CaptureResponse { pub reference_id: Option, } -impl PaymentsRequest { - pub fn get_feature_metadata_as_value( - &self, - ) -> common_utils::errors::CustomResult< - Option, - common_utils::errors::ParsingError, - > { - self.feature_metadata - .as_ref() - .map(Encode::encode_to_value) - .transpose() - } - - pub fn get_connector_metadata_as_value( - &self, - ) -> common_utils::errors::CustomResult< - Option, - common_utils::errors::ParsingError, - > { - self.connector_metadata - .as_ref() - .map(Encode::encode_to_value) - .transpose() - } - - pub fn get_allowed_payment_method_types_as_value( - &self, - ) -> common_utils::errors::CustomResult< - Option, - common_utils::errors::ParsingError, - > { - self.allowed_payment_method_types - .as_ref() - .map(Encode::encode_to_value) - .transpose() - } - - pub fn get_order_details_as_value( - &self, - ) -> common_utils::errors::CustomResult< - Option>, - common_utils::errors::ParsingError, - > { - self.order_details - .as_ref() - .map(|od| { - od.iter() - .map(|order| order.encode_to_value().map(Secret::new)) - .collect::, _>>() - }) - .transpose() - } -} - #[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, Copy, PartialEq, Eq)] pub enum Amount { Value(NonZeroI64), @@ -1124,6 +1481,15 @@ pub struct MandateIds { pub mandate_reference_id: Option, } +impl MandateIds { + pub fn is_network_transaction_id_flow(&self) -> bool { + matches!( + self.mandate_reference_id, + Some(MandateReferenceId::NetworkMandateId(_)) + ) + } +} + #[derive(Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone)] pub enum MandateReferenceId { ConnectorMandateId(ConnectorMandateReferenceId), // mandate_id send by connector @@ -1140,12 +1506,59 @@ pub struct NetworkTokenWithNTIRef { #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, Eq, PartialEq)] pub struct ConnectorMandateReferenceId { - pub connector_mandate_id: Option, - pub payment_method_id: Option, - pub update_history: Option>, - pub mandate_metadata: Option, -} + connector_mandate_id: Option, + payment_method_id: Option, + update_history: Option>, + mandate_metadata: Option, + connector_mandate_request_reference_id: Option, +} + +impl ConnectorMandateReferenceId { + pub fn new( + connector_mandate_id: Option, + payment_method_id: Option, + update_history: Option>, + mandate_metadata: Option, + connector_mandate_request_reference_id: Option, + ) -> Self { + Self { + connector_mandate_id, + payment_method_id, + update_history, + mandate_metadata, + connector_mandate_request_reference_id, + } + } + + pub fn get_connector_mandate_id(&self) -> Option { + self.connector_mandate_id.clone() + } + pub fn get_payment_method_id(&self) -> Option { + self.payment_method_id.clone() + } + pub fn get_mandate_metadata(&self) -> Option { + self.mandate_metadata.clone() + } + pub fn get_connector_mandate_request_reference_id(&self) -> Option { + self.connector_mandate_request_reference_id.clone() + } + pub fn update( + &mut self, + connector_mandate_id: Option, + payment_method_id: Option, + update_history: Option>, + mandate_metadata: Option, + connector_mandate_request_reference_id: Option, + ) { + self.connector_mandate_id = connector_mandate_id.or(self.connector_mandate_id.clone()); + self.payment_method_id = payment_method_id.or(self.payment_method_id.clone()); + self.update_history = update_history.or(self.update_history.clone()); + self.mandate_metadata = mandate_metadata.or(self.mandate_metadata.clone()); + self.connector_mandate_request_reference_id = connector_mandate_request_reference_id + .or(self.connector_mandate_request_reference_id.clone()); + } +} #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Eq, PartialEq)] pub struct UpdateHistory { pub connector_mandate_id: Option, @@ -1396,8 +1809,11 @@ impl GetAddressFromPaymentMethodData for Card { } impl Card { - fn apply_additional_card_info(&self, additional_card_info: AdditionalCardInfo) -> Self { - Self { + fn apply_additional_card_info( + &self, + additional_card_info: AdditionalCardInfo, + ) -> Result> { + Ok(Self { card_number: self.card_number.clone(), card_exp_month: self.card_exp_month.clone(), card_exp_year: self.card_exp_year.clone(), @@ -1410,7 +1826,7 @@ impl Card { card_network: self .card_network .clone() - .or(additional_card_info.card_network), + .or(additional_card_info.card_network.clone()), card_type: self.card_type.clone().or(additional_card_info.card_type), card_issuing_country: self .card_issuing_country @@ -1418,7 +1834,7 @@ impl Card { .or(additional_card_info.card_issuing_country), bank_code: self.bank_code.clone().or(additional_card_info.bank_code), nick_name: self.nick_name.clone(), - } + }) } } @@ -1460,6 +1876,7 @@ pub enum PayLaterData { /// The token for the sdk workflow token: String, }, + KlarnaCheckout {}, /// For Affirm redirect as PayLater Option AffirmRedirect {}, /// For AfterpayClearpay redirect as PayLater Option @@ -1517,6 +1934,7 @@ impl GetAddressFromPaymentMethodData for PayLaterData { | Self::WalleyRedirect {} | Self::AlmaRedirect {} | Self::KlarnaSdk { .. } + | Self::KlarnaCheckout {} | Self::AffirmRedirect {} | Self::AtomeRedirect {} => None, } @@ -1638,6 +2056,7 @@ impl GetAddressFromPaymentMethodData for BankDebitData { } } +#[cfg(feature = "v1")] /// Custom serializer and deserializer for PaymentMethodData mod payment_method_data_serde { @@ -1704,14 +2123,29 @@ mod payment_method_data_serde { if inner_map.is_empty() { None } else { - Some( - serde_json::from_value::( - payment_method_data_value, - ) - .map_err(|serde_json_error| { - de::Error::custom(serde_json_error.to_string()) - })?, + let payment_method_data = serde_json::from_value::( + payment_method_data_value, ) + .map_err(|serde_json_error| { + de::Error::custom(serde_json_error.to_string()) + })?; + let address_details = parsed_value + .billing + .as_ref() + .and_then(|billing| billing.address.clone()); + match (payment_method_data.clone(), address_details.as_ref()) { + ( + PaymentMethodData::Card(ref mut card), + Some(billing_address_details), + ) => { + if card.card_holder_name.is_none() { + card.card_holder_name = + billing_address_details.get_optional_full_name(); + } + Some(PaymentMethodData::Card(card.clone())) + } + _ => Some(payment_method_data), + } } } else { Err(de::Error::custom("Expected a map for payment_method_data"))? @@ -1757,6 +2191,7 @@ mod payment_method_data_serde { | PaymentMethodData::BankRedirect(_) | PaymentMethodData::BankTransfer(_) | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) | PaymentMethodData::CardToken(_) | PaymentMethodData::Crypto(_) | PaymentMethodData::GiftCard(_) @@ -1782,6 +2217,9 @@ mod payment_method_data_serde { /// The payment method information provided for making a payment #[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema, Eq, PartialEq)] pub struct PaymentMethodDataRequest { + /// This field is optional because, in case of saved cards we pass the payment_token + /// There might be cases where we don't need to pass the payment_method_data and pass only payment method billing details + /// We have flattened it because to maintain backwards compatibility with the old API contract #[serde(flatten)] pub payment_method_data: Option, /// billing details for the payment method. @@ -1825,6 +2263,8 @@ pub enum PaymentMethodData { CardToken(CardToken), #[schema(title = "OpenBanking")] OpenBanking(OpenBankingData), + #[schema(title = "MobilePayment")] + MobilePayment(MobilePaymentData), } pub trait GetAddressFromPaymentMethodData { @@ -1849,7 +2289,8 @@ impl GetAddressFromPaymentMethodData for PaymentMethodData { | Self::GiftCard(_) | Self::CardToken(_) | Self::OpenBanking(_) - | Self::MandatePayment => None, + | Self::MandatePayment + | Self::MobilePayment(_) => None, } } } @@ -1858,16 +2299,16 @@ impl PaymentMethodData { pub fn apply_additional_payment_data( &self, additional_payment_data: AdditionalPaymentData, - ) -> Self { + ) -> Result> { if let AdditionalPaymentData::Card(additional_card_info) = additional_payment_data { match self { - Self::Card(card) => { - Self::Card(card.apply_additional_card_info(*additional_card_info)) - } - _ => self.to_owned(), + Self::Card(card) => Ok(Self::Card( + card.apply_additional_card_info(*additional_card_info)?, + )), + _ => Ok(self.to_owned()), } } else { - self.to_owned() + Ok(self.to_owned()) } } @@ -1887,6 +2328,7 @@ impl PaymentMethodData { Self::Voucher(_) => Some(api_enums::PaymentMethod::Voucher), Self::GiftCard(_) => Some(api_enums::PaymentMethod::GiftCard), Self::OpenBanking(_) => Some(api_enums::PaymentMethod::OpenBanking), + Self::MobilePayment(_) => Some(api_enums::PaymentMethod::MobilePayment), Self::CardToken(_) | Self::MandatePayment => None, } } @@ -1907,6 +2349,14 @@ impl GetPaymentMethodType for CardRedirectData { } } +impl GetPaymentMethodType for MobilePaymentData { + fn get_payment_method_type(&self) -> api_enums::PaymentMethodType { + match self { + Self::DirectCarrierBilling { .. } => api_enums::PaymentMethodType::DirectCarrierBilling, + } + } +} + impl GetPaymentMethodType for WalletData { fn get_payment_method_type(&self) -> api_enums::PaymentMethodType { match self { @@ -1926,6 +2376,7 @@ impl GetPaymentMethodType for WalletData { Self::MbWayRedirect(_) => api_enums::PaymentMethodType::MbWay, Self::MobilePayRedirect(_) => api_enums::PaymentMethodType::MobilePay, Self::PaypalRedirect(_) | Self::PaypalSdk(_) => api_enums::PaymentMethodType::Paypal, + Self::Paze(_) => api_enums::PaymentMethodType::Paze, Self::SamsungPay(_) => api_enums::PaymentMethodType::SamsungPay, Self::TwintRedirect {} => api_enums::PaymentMethodType::Twint, Self::VippsRedirect {} => api_enums::PaymentMethodType::Vipps, @@ -1945,6 +2396,7 @@ impl GetPaymentMethodType for PayLaterData { match self { Self::KlarnaRedirect { .. } => api_enums::PaymentMethodType::Klarna, Self::KlarnaSdk { .. } => api_enums::PaymentMethodType::Klarna, + Self::KlarnaCheckout {} => api_enums::PaymentMethodType::Klarna, Self::AffirmRedirect {} => api_enums::PaymentMethodType::Affirm, Self::AfterpayClearpayRedirect { .. } => api_enums::PaymentMethodType::AfterpayClearpay, Self::PayBrightRedirect {} => api_enums::PaymentMethodType::PayBright, @@ -2195,10 +2647,13 @@ pub enum AdditionalPaymentData { #[serde(flatten)] details: Option, }, + MobilePayment { + #[serde(flatten)] + details: Option, + }, } #[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] - pub struct KlarnaSdkPaymentMethod { pub payment_type: Option, } @@ -2245,7 +2700,6 @@ pub enum BankRedirectData { Giropay { /// The billing details for bank redirection billing_details: Option, - /// Bank account details for Giropay #[schema(value_type = Option)] /// Bank account bic code @@ -2815,6 +3269,8 @@ pub enum WalletData { PaypalRedirect(PaypalRedirection), /// The wallet data for Paypal PaypalSdk(PayPalWalletData), + /// The wallet data for Paze + Paze(PazeWalletData), /// The wallet data for Samsung Pay SamsungPay(Box), /// Wallet data for Twint Redirection @@ -2875,6 +3331,7 @@ impl GetAddressFromPaymentMethodData for WalletData { | Self::GooglePayRedirect(_) | Self::GooglePayThirdPartySdk(_) | Self::PaypalSdk(_) + | Self::Paze(_) | Self::SamsungPay(_) | Self::TwintRedirect {} | Self::VippsRedirect {} @@ -2887,21 +3344,69 @@ impl GetAddressFromPaymentMethodData for WalletData { } } +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub struct PazeWalletData { + #[schema(value_type = String)] + pub complete_response: Secret, +} + #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] #[serde(rename_all = "snake_case")] pub struct SamsungPayWalletData { pub payment_credential: SamsungPayWalletCredentials, } +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +#[serde(rename_all = "snake_case", untagged)] +pub enum SamsungPayWalletCredentials { + SamsungPayWalletDataForWeb(SamsungPayWebWalletData), + SamsungPayWalletDataForApp(SamsungPayAppWalletData), +} + +impl From for common_enums::SamsungPayCardBrand { + fn from(samsung_pay_card_brand: SamsungPayCardBrand) -> Self { + match samsung_pay_card_brand { + SamsungPayCardBrand::Visa => Self::Visa, + SamsungPayCardBrand::MasterCard => Self::MasterCard, + SamsungPayCardBrand::Amex => Self::Amex, + SamsungPayCardBrand::Discover => Self::Discover, + SamsungPayCardBrand::Unknown => Self::Unknown, + } + } +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub struct SamsungPayAppWalletData { + /// Samsung Pay token data + #[serde(rename = "3_d_s")] + pub token_data: SamsungPayTokenData, + /// Brand of the payment card + pub payment_card_brand: SamsungPayCardBrand, + /// Currency type of the payment + pub payment_currency_type: String, + /// Last 4 digits of the device specific card number + pub payment_last4_dpan: Option, + /// Last 4 digits of the card number + pub payment_last4_fpan: String, + /// Merchant reference id that was passed in the session call request + pub merchant_ref: Option, + /// Specifies authentication method used + pub method: Option, + /// Value if credential is enabled for recurring payment + pub recurring_payment: Option, +} + #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] #[serde(rename_all = "snake_case")] -pub struct SamsungPayWalletCredentials { +pub struct SamsungPayWebWalletData { /// Specifies authentication method used pub method: Option, /// Value if credential is enabled for recurring payment pub recurring_payment: Option, /// Brand of the payment card - pub card_brand: String, + pub card_brand: SamsungPayCardBrand, /// Last 4 digits of the card number #[serde(rename = "card_last4digits")] pub card_last_four_digits: String, @@ -2923,12 +3428,41 @@ pub struct SamsungPayTokenData { pub data: Secret, } +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +#[serde(rename_all = "lowercase")] +pub enum SamsungPayCardBrand { + #[serde(alias = "VI")] + Visa, + #[serde(alias = "MC")] + MasterCard, + #[serde(alias = "AX")] + Amex, + #[serde(alias = "DC")] + Discover, + #[serde(other)] + Unknown, +} + #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] #[serde(rename_all = "snake_case")] pub enum OpenBankingData { #[serde(rename = "open_banking_pis")] OpenBankingPIS {}, } + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum MobilePaymentData { + DirectCarrierBilling { + /// The phone number of the user + #[schema(value_type = String, example = "1234567890")] + msisdn: String, + /// Unique user id + #[schema(value_type = Option, example = "02iacdYXGI9CnyJdoN8c7")] + client_uid: Option, + }, +} + #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] #[serde(rename_all = "snake_case")] pub struct GooglePayWalletData { @@ -3199,6 +3733,7 @@ where | PaymentMethodDataResponse::GiftCard(_) | PaymentMethodDataResponse::PayLater(_) | PaymentMethodDataResponse::RealTimePayment(_) + | PaymentMethodDataResponse::MobilePayment(_) | PaymentMethodDataResponse::Upi(_) | PaymentMethodDataResponse::Wallet(_) | PaymentMethodDataResponse::BankTransfer(_) @@ -3235,6 +3770,7 @@ pub enum PaymentMethodDataResponse { CardRedirect(Box), CardToken(Box), OpenBanking(Box), + MobilePayment(Box), } #[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)] @@ -3294,6 +3830,12 @@ pub struct OpenBankingResponse { details: Option, } +#[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct MobilePaymentResponse { + #[serde(flatten)] + details: Option, +} + #[derive(Eq, PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)] pub struct RealTimePaymentDataResponse { #[serde(flatten)] @@ -3324,6 +3866,7 @@ pub struct WalletResponse { details: Option, } +/// Hyperswitch supports SDK integration with Apple Pay and Google Pay wallets. For other wallets, we integrate with their respective connectors, redirecting the customer to the connector for wallet payments. As a result, we don’t receive any payment method data in the confirm call for payments made through other wallets. #[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)] #[serde(rename_all = "snake_case")] pub enum WalletResponseData { @@ -3334,7 +3877,6 @@ pub enum WalletResponseData { } #[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)] - pub struct KlarnaSdkPaymentMethodResponse { pub payment_type: Option, } @@ -3348,6 +3890,7 @@ pub struct PaymentMethodDataResponseWithBilling { } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, ToSchema)] +#[cfg(feature = "v1")] pub enum PaymentIdType { /// The identifier for payment intent PaymentIntentId(id_type::PaymentId), @@ -3359,6 +3902,20 @@ pub enum PaymentIdType { PreprocessingId(String), } +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, ToSchema)] +#[cfg(feature = "v2")] +pub enum PaymentIdType { + /// The identifier for payment intent + PaymentIntentId(id_type::GlobalPaymentId), + /// The identifier for connector transaction + ConnectorTransactionId(String), + /// The identifier for payment attempt + PaymentAttemptId(String), + /// The identifier for preprocessing step + PreprocessingId(String), +} + +#[cfg(feature = "v1")] impl fmt::Display for PaymentIdType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -3383,6 +3940,7 @@ impl fmt::Display for PaymentIdType { } } +#[cfg(feature = "v1")] impl Default for PaymentIdType { fn default() -> Self { Self::PaymentIntentId(Default::default()) @@ -3401,6 +3959,8 @@ pub struct Address { pub email: Option, } +impl masking::SerializableSecret for Address {} + impl Address { /// Unify the address, giving priority to `self` when details are present in both pub fn unify_address(self, other: Option<&Self>) -> Self { @@ -3518,54 +4078,6 @@ pub struct EncryptableAddressDetails { pub email: crypto::OptionalEncryptableEmail, } -impl ToEncryptable, Secret> - for AddressDetailsWithPhone -{ - fn from_encryptable( - mut hashmap: FxHashMap>>, - ) -> common_utils::errors::CustomResult< - EncryptableAddressDetails, - common_utils::errors::ParsingError, - > { - Ok(EncryptableAddressDetails { - line1: hashmap.remove("line1"), - line2: hashmap.remove("line2"), - line3: hashmap.remove("line3"), - state: hashmap.remove("state"), - zip: hashmap.remove("zip"), - first_name: hashmap.remove("first_name"), - last_name: hashmap.remove("last_name"), - phone_number: hashmap.remove("phone_number"), - email: hashmap.remove("email").map(|x| { - let inner: Secret = x.clone().into_inner().switch_strategy(); - crypto::Encryptable::new(inner, x.into_encrypted()) - }), - }) - } - - fn to_encryptable(self) -> FxHashMap> { - let mut map = FxHashMap::with_capacity_and_hasher(9, Default::default()); - self.address.map(|address| { - address.line1.map(|x| map.insert("line1".to_string(), x)); - address.line2.map(|x| map.insert("line2".to_string(), x)); - address.line3.map(|x| map.insert("line3".to_string(), x)); - address.state.map(|x| map.insert("state".to_string(), x)); - address.zip.map(|x| map.insert("zip".to_string(), x)); - address - .first_name - .map(|x| map.insert("first_name".to_string(), x)); - address - .last_name - .map(|x| map.insert("last_name".to_string(), x)); - }); - self.email - .map(|x| map.insert("email".to_string(), x.expose().switch_strategy())); - self.phone_number - .map(|x| map.insert("phone_number".to_string(), x)); - map - } -} - #[derive(Debug, Clone, Default, Eq, PartialEq, ToSchema, serde::Deserialize, serde::Serialize)] pub struct PhoneDetails { /// The contact number @@ -3576,6 +4088,7 @@ pub struct PhoneDetails { pub country_code: Option, } +#[cfg(feature = "v1")] #[derive(Debug, Clone, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)] pub struct PaymentsCaptureRequest { /// The unique identifier for the payment @@ -3584,7 +4097,7 @@ pub struct PaymentsCaptureRequest { /// The unique identifier for the merchant #[schema(value_type = Option)] pub merchant_id: Option, - /// The Amount to be captured/ debited from the user's payment method. + /// The Amount to be captured/ debited from the user's payment method. If not passed the full amount will be captured. #[schema(value_type = i64, example = 6540)] pub amount_to_capture: Option, /// Decider to refund the uncaptured amount @@ -3598,6 +4111,28 @@ pub struct PaymentsCaptureRequest { pub merchant_connector_details: Option, } +#[cfg(feature = "v2")] +#[derive(Debug, Clone, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct PaymentsCaptureRequest { + /// The Amount to be captured/ debited from the user's payment method. If not passed the full amount will be captured. + #[schema(value_type = Option, example = 6540)] + pub amount_to_capture: Option, +} + +#[cfg(feature = "v2")] +#[derive(Debug, Clone, serde::Serialize, ToSchema)] +pub struct PaymentsCaptureResponse { + /// The unique identifier for the payment + pub id: id_type::GlobalPaymentId, + + /// Status of the payment + #[schema(value_type = IntentStatus, example = "succeeded")] + pub status: common_enums::IntentStatus, + + /// Amount details related to the payment + pub amount: PaymentAmountDetailsResponse, +} + #[derive(Default, Clone, Debug, Eq, PartialEq, serde::Serialize)] pub struct UrlDetails { pub url: String, @@ -3616,15 +4151,23 @@ pub enum NextActionType { TriggerApi, DisplayBankTransferInformation, DisplayWaitScreen, + CollectOtp, } #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, ToSchema)] #[serde(tag = "type", rename_all = "snake_case")] pub enum NextActionData { /// Contains the url for redirection flow + #[cfg(feature = "v1")] RedirectToUrl { redirect_to_url: String, }, + /// Contains the url for redirection flow + #[cfg(feature = "v2")] + RedirectToUrl { + #[schema(value_type = String)] + redirect_to_url: Url, + }, /// Informs the next steps for bank transfer and also contains the charges details (ex: amount received, amount charged etc) DisplayBankTransferInformation { bank_transfer_steps_and_charges_details: BankTransferNextStepsData, @@ -3665,6 +4208,10 @@ pub enum NextActionData { InvokeSdkClient { next_action_data: SdkNextActionData, }, + /// Contains consent to collect otp for mobile payment + CollectOtp { + consent_data_required: MobilePaymentConsent, + }, } #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, ToSchema)] @@ -3731,6 +4278,7 @@ pub enum QrCodeInformation { #[serde(rename_all = "snake_case")] pub struct SdkNextActionData { pub next_action: NextActionCall, + pub order_id: Option, } #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] @@ -3759,6 +4307,20 @@ pub struct VoucherNextStepData { pub instructions_url: Option, } +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct MobilePaymentNextStepData { + /// is consent details required to be shown by sdk + pub consent_data_required: MobilePaymentConsent, +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum MobilePaymentConsent { + ConsentRequired, + ConsentNotRequired, + ConsentOptional, +} + #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] pub struct QrCodeNextStepsInstruction { pub image_data_url: Url, @@ -3796,6 +4358,8 @@ pub struct SepaBankTransferInstructions { pub country: String, #[schema(value_type = String, example = "123456789")] pub iban: Secret, + #[schema(value_type = String, example = "U2PVVSEV4V9Y")] + pub reference: Secret, } #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)] @@ -3849,7 +4413,6 @@ pub struct ReceiverDetails { #[derive(Clone, Debug, PartialEq, serde::Serialize, ToSchema, router_derive::PolymorphicSchema)] #[generate_schemas(PaymentsCreateResponseOpenApi)] - pub struct PaymentsResponse { /// Unique identifier for the payment. This ensures idempotency for multiple payments /// that have been done by a single merchant. @@ -3878,6 +4441,10 @@ pub struct PaymentsResponse { #[schema(value_type = i64, example = 6540)] pub net_amount: MinorUnit, + /// The shipping cost for the payment. + #[schema(value_type = Option, example = 6540)] + pub shipping_cost: Option, + /// The maximum amount that could be captured from the payment #[schema(value_type = i64, minimum = 100, example = 6540)] pub amount_capturable: MinorUnit, @@ -4153,37 +4720,280 @@ pub struct PaymentsResponse { #[schema(value_type = Option)] pub payment_method_status: Option, - /// Date time at which payment was updated - #[schema(example = "2022-09-10T10:11:12Z")] - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] - pub updated: Option, + /// Date time at which payment was updated + #[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 split_payments: 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, + + /// Merchant's identifier for the payment/invoice. This will be sent to the connector + /// if the connector provides support to accept multiple reference ids. + /// In case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference. + #[schema( + value_type = Option, + max_length = 255, + example = "Custom_Order_id_123" + )] + pub merchant_order_reference_id: Option, + /// order tax amount calculated by tax connectors + pub order_tax_amount: Option, + + /// Connector Identifier for the payment method + pub connector_mandate_id: Option, +} + +// Serialize is implemented because, this will be serialized in the api events. +// Usually request types should not have serialize implemented. +// +/// Request for Payment Intent Confirm +#[cfg(feature = "v2")] +#[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema)] +#[serde(deny_unknown_fields)] +pub struct PaymentsConfirmIntentRequest { + /// The URL to which you want the user to be redirected after the completion of the payment operation + /// If this url is not passed, the url configured in the business profile will be used + #[schema(value_type = Option, example = "https://hyperswitch.io")] + pub return_url: Option, + + /// The payment instrument data to be used for the payment + pub payment_method_data: PaymentMethodDataRequest, + + /// The payment method type to be used for the payment. This should match with the `payment_method_data` provided + #[schema(value_type = PaymentMethod, example = "card")] + pub payment_method_type: api_enums::PaymentMethod, + + /// The payment method subtype to be used for the payment. This should match with the `payment_method_data` provided + #[schema(value_type = PaymentMethodType, example = "apple_pay")] + pub payment_method_subtype: api_enums::PaymentMethodType, + + /// The shipping address for the payment. This will override the shipping address provided in the create-intent request + pub shipping: Option
, + + /// This "CustomerAcceptance" object is passed during Payments-Confirm request, it enlists the type, time, and mode of acceptance properties related to an acceptance done by the customer. The customer_acceptance sub object is usually passed by the SDK or client. + #[schema(value_type = Option)] + pub customer_acceptance: Option, + + /// Additional details required by 3DS 2.0 + #[schema(value_type = Option)] + pub browser_info: Option, +} + +// Serialize is implemented because, this will be serialized in the api events. +// Usually request types should not have serialize implemented. +// +/// Request for Payment Status +#[cfg(feature = "v2")] +#[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct PaymentsRetrieveRequest { + /// A boolean used to indicate if the payment status should be fetched from the connector + /// If this is set to true, the status will be fetched from the connector + #[serde(default)] + pub force_sync: bool, + + /// These are the query params that are sent in case of redirect response. + /// These can be ingested by the connector to take necessary actions. + pub param: Option, +} + +/// Error details for the payment +#[cfg(feature = "v2")] +#[derive(Debug, serde::Serialize, Clone, ToSchema)] +pub struct ErrorDetails { + /// The error code + pub code: String, + /// The error message + pub message: String, + /// The unified error code across all connectors. + /// This can be relied upon for taking decisions based on the error. + pub unified_code: Option, + /// The unified error message across all connectors. + /// If there is a translation available, this will have the translated message + pub unified_message: Option, +} + +/// Response for Payment Intent Confirm +#[cfg(feature = "v2")] +#[derive(Debug, serde::Serialize, ToSchema)] +pub struct PaymentsConfirmIntentResponse { + /// Unique identifier for the payment. This ensures idempotency for multiple payments + /// that have been done by a single merchant. + #[schema( + min_length = 32, + max_length = 64, + example = "12345_pay_01926c58bc6e77c09e809964e72af8c8", + value_type = String, + )] + pub id: id_type::GlobalPaymentId, + + #[schema(value_type = IntentStatus, example = "success")] + pub status: api_enums::IntentStatus, + + /// Amount related information for this payment and attempt + pub amount: PaymentAmountDetailsResponse, + + /// The identifier for the customer + #[schema( + min_length = 32, + max_length = 64, + example = "12345_cus_01926c58bc6e77c09e809964e72af8c8", + value_type = String + )] + pub customer_id: Option, + + /// The connector used for the payment + #[schema(example = "stripe")] + pub connector: String, + + /// It's a token used for client side verification. + #[schema(value_type = String)] + pub client_secret: common_utils::types::ClientSecret, + + /// Time when the payment was created + #[schema(example = "2022-09-10T10:11:12Z")] + #[serde(with = "common_utils::custom_serde::iso8601")] + pub created: PrimitiveDateTime, + + /// The payment method information provided for making a payment + #[schema(value_type = Option)] + #[serde(serialize_with = "serialize_payment_method_data_response")] + pub payment_method_data: Option, + + /// The payment method type for this payment attempt + #[schema(value_type = PaymentMethod, example = "wallet")] + pub payment_method_type: api_enums::PaymentMethod, + + #[schema(value_type = PaymentMethodType, example = "apple_pay")] + pub payment_method_subtype: api_enums::PaymentMethodType, + + /// Additional information required for redirection + pub next_action: Option, + + /// A unique identifier for a payment provided by the connector + #[schema(value_type = Option, example = "993672945374576J")] + pub connector_transaction_id: Option, + + /// reference(Identifier) to the payment at connector side + #[schema(value_type = Option, example = "993672945374576J")] + pub connector_reference_id: Option, + + /// Identifier of the connector ( merchant connector account ) which was chosen to make the payment + #[schema(value_type = String)] + pub merchant_connector_id: id_type::MerchantConnectorAccountId, + + /// The browser information used for this payment + #[schema(value_type = Option)] + pub browser_info: Option, + + /// Error details for the payment if any + pub error: Option, +} + +// TODO: have a separate response for detailed, summarized +/// Response for Payment Intent Confirm +#[cfg(feature = "v2")] +#[derive(Debug, serde::Serialize, Clone, ToSchema)] +pub struct PaymentsRetrieveResponse { + /// Unique identifier for the payment. This ensures idempotency for multiple payments + /// that have been done by a single merchant. + #[schema( + min_length = 32, + max_length = 64, + example = "12345_pay_01926c58bc6e77c09e809964e72af8c8", + value_type = String, + )] + pub id: id_type::GlobalPaymentId, + + #[schema(value_type = IntentStatus, example = "succeeded")] + pub status: api_enums::IntentStatus, + + /// Amount related information for this payment and attempt + pub amount: PaymentAmountDetailsResponse, + + /// The identifier for the customer + #[schema( + min_length = 32, + max_length = 64, + example = "12345_cus_01926c58bc6e77c09e809964e72af8c8", + value_type = String + )] + pub customer_id: Option, + + /// The connector used for the payment + #[schema(example = "stripe")] + pub connector: Option, + + /// It's a token used for client side verification. + #[schema(value_type = String)] + pub client_secret: common_utils::types::ClientSecret, + + /// Time when the payment was created + #[schema(example = "2022-09-10T10:11:12Z")] + #[serde(with = "common_utils::custom_serde::iso8601")] + pub created: PrimitiveDateTime, + + /// The payment method information provided for making a payment + #[schema(value_type = Option)] + #[serde(serialize_with = "serialize_payment_method_data_response")] + pub payment_method_data: Option, + + /// The payment method type for this payment attempt + #[schema(value_type = Option, example = "wallet")] + pub payment_method_type: Option, + + #[schema(value_type = Option, example = "apple_pay")] + pub payment_method_subtype: Option, + + /// A unique identifier for a payment provided by the connector + #[schema(value_type = Option, example = "993672945374576J")] + pub connector_transaction_id: Option, + + /// reference(Identifier) to the payment at connector side + #[schema(value_type = Option, example = "993672945374576J")] + pub connector_reference_id: Option, + + /// Identifier of the connector ( merchant connector account ) which was chosen to make the payment + #[schema(value_type = Option)] + pub merchant_connector_id: Option, + + /// The browser information used for this payment + #[schema(value_type = Option)] + pub browser_info: Option, - /// Fee information to be charged on the payment being collected - pub charges: Option, + /// Error details for the payment if any + pub error: 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, + /// The shipping address associated with the payment intent + pub shipping: Option
, - /// Merchant's identifier for the payment/invoice. This will be sent to the connector - /// if the connector provides support to accept multiple reference ids. - /// In case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference. - #[schema( - value_type = Option, - max_length = 255, - example = "Custom_Order_id_123" - )] - pub merchant_order_reference_id: Option, - /// order tax amount calculated by tax connectors - pub order_tax_amount: Option, + /// The billing address associated with the payment intent + pub billing: Option
, +} - /// Connector Identifier for the payment method - pub connector_mandate_id: Option, +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[cfg(feature = "v2")] +pub struct PaymentStartRedirectionRequest { + /// Global Payment ID + pub id: id_type::GlobalPaymentId, +} + +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +#[cfg(feature = "v2")] +pub struct PaymentStartRedirectionParams { + /// The identifier for the Merchant Account. + pub publishable_key: String, + /// The identifier for business profile + pub profile_id: id_type::ProfileId, } /// Fee information to be charged on the payment being collected -#[derive(Setter, Clone, Default, Debug, PartialEq, serde::Serialize, ToSchema)] -pub struct PaymentChargeResponse { +#[derive(Setter, Clone, Debug, PartialEq, serde::Serialize, ToSchema)] +pub struct StripeSplitPaymentsResponse { /// Identifier for charge created for the payment pub charge_id: Option, @@ -4199,6 +5009,13 @@ pub struct PaymentChargeResponse { pub transfer_account_id: String, } +#[derive(Clone, Debug, PartialEq, serde::Serialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum SplitPaymentsResponse { + /// StripeSplitPaymentsResponse + StripeSplitPayment(StripeSplitPaymentsResponse), +} + /// Details of external authentication #[derive(Setter, Clone, Default, Debug, PartialEq, serde::Serialize, ToSchema)] pub struct ExternalAuthenticationDetailsResponse { @@ -4355,7 +5172,23 @@ pub struct PaymentListFilterConstraints { /// The order in which payments list should be sorted #[serde(default)] pub order: Order, + /// The List of all the card networks to filter payments list + pub card_network: Option>, + /// The identifier for merchant order reference id + pub merchant_order_reference_id: Option, +} + +impl PaymentListFilterConstraints { + pub fn has_no_attempt_filters(&self) -> bool { + self.connector.is_none() + && self.payment_method.is_none() + && self.payment_method_type.is_none() + && self.authentication_type.is_none() + && self.merchant_connector_id.is_none() + && self.card_network.is_none() + } } + #[derive(Clone, Debug, serde::Serialize)] pub struct PaymentListFilters { /// The list of available connector filters @@ -4384,6 +5217,8 @@ pub struct PaymentListFiltersV2 { pub payment_method: HashMap>, /// The list of available authentication types pub authentication_type: Vec, + /// The list of available card networks + pub card_network: Vec, } #[derive(Clone, Debug, serde::Serialize)] @@ -4462,6 +5297,7 @@ pub struct MandateValidationFields { pub off_session: Option, } +#[cfg(feature = "v1")] impl From<&PaymentsRequest> for MandateValidationFields { fn from(req: &PaymentsRequest) -> Self { let recurring_details = req @@ -4499,6 +5335,19 @@ impl From<&VerifyRequest> for MandateValidationFields { } } +// #[cfg(all(feature = "v2", feature = "payment_v2"))] +// impl From for PaymentsSessionResponse { +// fn from(item: PaymentsSessionRequest) -> Self { +// let client_secret: Secret = Secret::new(item.client_secret); +// Self { +// session_token: vec![], +// payment_id: item.payment_id, +// client_secret, +// } +// } +// } + +#[cfg(feature = "v1")] impl From for PaymentsSessionResponse { fn from(item: PaymentsSessionRequest) -> Self { let client_secret: Secret = Secret::new(item.client_secret); @@ -4510,6 +5359,7 @@ impl From for PaymentsSessionResponse { } } +#[cfg(feature = "v1")] impl From for PaymentsRequest { fn from(item: PaymentsStartRequest) -> Self { Self { @@ -4617,6 +5467,9 @@ impl From for PaymentMethodDataResponse { AdditionalPaymentData::OpenBanking { details } => { Self::OpenBanking(Box::new(OpenBankingResponse { details })) } + AdditionalPaymentData::MobilePayment { details } => { + Self::MobilePayment(Box::new(MobilePaymentResponse { details })) + } } } } @@ -4630,6 +5483,7 @@ pub struct PgRedirectResponse { pub amount: Option, } +#[cfg(feature = "v1")] #[derive(Debug, serde::Serialize, PartialEq, Eq, serde::Deserialize)] pub struct RedirectionResponse { pub return_url: String, @@ -4639,6 +5493,12 @@ pub struct RedirectionResponse { pub headers: Vec<(String, String)>, } +#[cfg(feature = "v2")] +#[derive(Debug, serde::Serialize, PartialEq, Eq, serde::Deserialize)] +pub struct RedirectionResponse { + pub return_url_with_query_params: String, +} + #[derive(Debug, serde::Deserialize)] pub struct PaymentsResponseForm { pub transaction_id: String, @@ -4647,6 +5507,7 @@ pub struct PaymentsResponseForm { pub order_id: String, } +#[cfg(feature = "v1")] #[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] pub struct PaymentsRetrieveRequest { /// The type of ID (ex: payment intent id, payment attempt id or connector txn id) @@ -4672,7 +5533,7 @@ pub struct PaymentsRetrieveRequest { pub expand_attempts: Option, } -#[derive(Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +#[derive(Debug, Default, PartialEq, serde::Deserialize, serde::Serialize, Clone, ToSchema)] pub struct OrderDetailsWithAmount { /// Name of the product that is being purchased #[schema(max_length = 255, example = "shirt")] @@ -4681,7 +5542,13 @@ pub struct OrderDetailsWithAmount { #[schema(example = 1)] pub quantity: u16, /// the amount per quantity of product - pub amount: i64, + #[schema(value_type = i64)] + pub amount: MinorUnit, + /// tax rate applicable to the product + pub tax_rate: Option, + /// total tax amount applicable to the product + #[schema(value_type = Option)] + pub total_tax_amount: Option, // Does the order includes shipping pub requires_shipping: Option, /// The image URL of the product @@ -4700,43 +5567,7 @@ pub struct OrderDetailsWithAmount { pub product_tax_code: Option, } -#[derive(Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, Clone, ToSchema)] -#[serde(rename_all = "snake_case")] -pub enum ProductType { - #[default] - Physical, - Digital, - Travel, - Ride, - Event, - Accommodation, -} - -#[derive(Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, Clone, ToSchema)] -pub struct OrderDetails { - /// Name of the product that is being purchased - #[schema(max_length = 255, example = "shirt")] - pub product_name: String, - /// The quantity of the product to be purchased - #[schema(example = 1)] - pub quantity: u16, - // Does the order include shipping - pub requires_shipping: Option, - /// The image URL of the product - pub product_img_link: Option, - /// ID of the product that is being purchased - pub product_id: Option, - /// Category of the product that is being purchased - pub category: Option, - /// Sub category of the product that is being purchased - pub sub_category: Option, - /// Brand of the product that is being purchased - pub brand: Option, - /// Type of the product that is being purchased - pub product_type: Option, - /// The tax code for the product - pub product_tax_code: Option, -} +impl masking::SerializableSecret for OrderDetailsWithAmount {} #[derive(Default, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, Clone, ToSchema)] pub struct RedirectResponse { @@ -4746,6 +5577,11 @@ pub struct RedirectResponse { pub json_payload: Option, } +#[cfg(feature = "v2")] +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +pub struct PaymentsSessionRequest {} + +#[cfg(feature = "v1")] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] pub struct PaymentsSessionRequest { /// The identifier for the payment @@ -4761,6 +5597,34 @@ pub struct PaymentsSessionRequest { pub merchant_connector_details: Option, } +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +pub struct PaymentsPostSessionTokensRequest { + /// The unique identifier for the payment + #[serde(skip_deserializing)] + #[schema(value_type = String)] + pub payment_id: id_type::PaymentId, + /// It's a token used for client side verification. + #[schema(value_type = String)] + pub client_secret: Secret, + /// Payment method type + #[schema(value_type = PaymentMethodType)] + pub payment_method_type: api_enums::PaymentMethodType, + /// The payment method that is to be used for the payment + #[schema(value_type = PaymentMethod, example = "card")] + pub payment_method: api_enums::PaymentMethod, +} + +#[derive(Debug, serde::Serialize, Clone, ToSchema)] +pub struct PaymentsPostSessionTokensResponse { + /// The identifier for the payment + #[schema(value_type = String)] + pub payment_id: id_type::PaymentId, + /// Additional information required for redirection + pub next_action: Option, + #[schema(value_type = IntentStatus, example = "failed", default = "requires_confirmation")] + pub status: api_enums::IntentStatus, +} + #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)] pub struct PaymentsDynamicTaxCalculationRequest { /// The unique identifier for the payment @@ -4775,6 +5639,8 @@ pub struct PaymentsDynamicTaxCalculationRequest { /// Payment method type #[schema(value_type = PaymentMethodType)] pub payment_method_type: api_enums::PaymentMethodType, + /// Session Id + pub session_id: Option, } #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)] @@ -4908,12 +5774,25 @@ pub struct GpaySessionTokenData { pub data: GpayMetaData, } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct PazeSessionTokenData { + #[serde(rename = "paze")] + pub data: PazeMetadata, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct PazeMetadata { + pub client_id: String, + pub client_name: String, + pub client_profile_id: String, +} + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "snake_case")] pub enum SamsungPayCombinedMetadata { // This is to support the Samsung Pay decryption flow with application credentials, // where the private key, certificates, or any other information required for decryption - // will be obtained from the environment variables. + // will be obtained from the application configuration. ApplicationCredentials(SamsungPayApplicationCredentials), MerchantCredentials(SamsungPayMerchantCredentials), } @@ -5114,10 +5993,34 @@ pub enum SessionToken { ApplePay(Box), /// Session token for OpenBanking PIS flow OpenBanking(OpenBankingSessionToken), + /// The session response structure for Paze + Paze(Box), + /// The sessions response structure for ClickToPay + ClickToPay(Box), /// Whenever there is no session token response or an error in session response NoSessionTokenReceived, } +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] +#[serde(rename_all = "lowercase")] +pub struct PazeSessionTokenResponse { + /// Paze Client ID + pub client_id: String, + /// Client Name to be displayed on the Paze screen + pub client_name: String, + /// Paze Client Profile ID + pub client_profile_id: String, + /// The transaction currency code + #[schema(value_type = Currency, example = "USD")] + pub transaction_currency_code: api_enums::Currency, + /// The transaction amount + #[schema(value_type = String, example = "38.02")] + pub transaction_amount: StringMajorUnit, + /// Email Address + #[schema(max_length = 255, value_type = Option, example = "johntest@test.com")] + pub email_address: Option, +} + #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] #[serde(untagged)] pub enum GpaySessionTokenResponse { @@ -5194,8 +6097,8 @@ pub enum SamsungPayProtocolType { pub struct SamsungPayMerchantPaymentInformation { /// Merchant name, this will be displayed on the Samsung Pay screen pub name: String, - /// Merchant domain that process payments - pub url: String, + /// Merchant domain that process payments, required for web payments + pub url: Option, /// Merchant country code #[schema(value_type = CountryAlpha2, example = "US")] pub country_code: api_enums::CountryAlpha2, @@ -5291,6 +6194,8 @@ pub struct SdkNextAction { #[derive(Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, Clone, ToSchema)] #[serde(rename_all = "snake_case")] pub enum NextActionCall { + /// The next action call is Post Session Tokens + PostSessionTokens, /// The next action call is confirm Confirm, /// The next action call is sync @@ -5374,6 +6279,57 @@ pub struct ApplePayPaymentRequest { #[serde(skip_serializing_if = "Option::is_none")] /// The required shipping contacht fields for connector pub required_shipping_contact_fields: Option, + /// Recurring payment request for apple pay Merchant Token + #[serde(skip_serializing_if = "Option::is_none")] + pub recurring_payment_request: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct ApplePayRecurringPaymentRequest { + /// A description of the recurring payment that Apple Pay displays to the user in the payment sheet + pub payment_description: String, + /// The regular billing cycle for the recurring payment, including start and end dates, an interval, and an interval count + pub regular_billing: ApplePayRegularBillingRequest, + /// A localized billing agreement that the payment sheet displays to the user before the user authorizes the payment + #[serde(skip_serializing_if = "Option::is_none")] + pub billing_agreement: Option, + /// A URL to a web page where the user can update or delete the payment method for the recurring payment + #[schema(value_type = String, example = "https://hyperswitch.io")] + pub management_url: common_utils::types::Url, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct ApplePayRegularBillingRequest { + /// The amount of the recurring payment + #[schema(value_type = String, example = "38.02")] + pub amount: StringMajorUnit, + /// The label that Apple Pay displays to the user in the payment sheet with the recurring details + pub label: String, + /// The time that the payment occurs as part of a successful transaction + pub payment_timing: ApplePayPaymentTiming, + /// The date of the first payment + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "common_utils::custom_serde::iso8601::option")] + pub recurring_payment_start_date: Option, + /// The date of the final payment + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "common_utils::custom_serde::iso8601::option")] + pub recurring_payment_end_date: Option, + /// The amount of time — in calendar units, such as day, month, or year — that represents a fraction of the total payment interval + #[serde(skip_serializing_if = "Option::is_none")] + pub recurring_payment_interval_unit: Option, + /// The number of interval units that make up the total payment interval + #[serde(skip_serializing_if = "Option::is_none")] + pub recurring_payment_interval_count: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum ApplePayPaymentTiming { + /// A value that specifies that the payment occurs when the transaction is complete + Immediate, + /// A value that specifies that the payment occurs on a regular basis + Recurring, } #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema, serde::Deserialize)] @@ -5408,6 +6364,7 @@ pub struct ApplepayErrorResponse { pub status_message: String, } +#[cfg(feature = "v1")] #[derive(Default, Debug, serde::Serialize, Clone, ToSchema)] pub struct PaymentsSessionResponse { /// The identifier for the payment @@ -5420,6 +6377,16 @@ pub struct PaymentsSessionResponse { pub session_token: Vec, } +#[cfg(feature = "v2")] +#[derive(Debug, serde::Serialize, Clone, ToSchema)] +pub struct PaymentsSessionResponse { + /// The identifier for the payment + #[schema(value_type = String)] + pub payment_id: id_type::GlobalPaymentId, + /// The list of session token object + pub session_token: Vec, +} + #[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)] pub struct PaymentRetrieveBody { /// The identifier for the Merchant Account. @@ -5583,6 +6550,23 @@ pub struct SdkInformation { pub sdk_max_timeout: u8, } +#[cfg(feature = "v2")] +#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)] +pub struct PaymentMethodsListRequest {} + +#[cfg(feature = "v2")] +#[derive(Debug, serde::Serialize, ToSchema)] +pub struct PaymentMethodListResponseForPayments { + /// The list of payment methods that are enabled for the business profile + #[schema(value_type = Vec)] + pub payment_methods_enabled: Vec, + + /// The list of payment methods that are saved by the given customer + /// This field is only returned if the customer_id is provided in the request + #[schema(value_type = Option>)] + pub customer_payment_methods: Option>, +} + #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)] pub struct PaymentsExternalAuthenticationResponse { /// Indicates the transaction status @@ -5640,6 +6624,49 @@ pub struct FeatureMetadata { /// Additional tags to be used for global search #[schema(value_type = Option>)] pub search_tags: Option>>, + /// Recurring payment details required for apple pay Merchant Token + pub apple_pay_recurring_details: Option, +} + +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct ApplePayRecurringDetails { + /// A description of the recurring payment that Apple Pay displays to the user in the payment sheet + pub payment_description: String, + /// The regular billing cycle for the recurring payment, including start and end dates, an interval, and an interval count + pub regular_billing: ApplePayRegularBillingDetails, + /// A localized billing agreement that the payment sheet displays to the user before the user authorizes the payment + pub billing_agreement: Option, + /// A URL to a web page where the user can update or delete the payment method for the recurring payment + #[schema(value_type = String, example = "https://hyperswitch.io")] + pub management_url: common_utils::types::Url, +} + +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct ApplePayRegularBillingDetails { + /// The label that Apple Pay displays to the user in the payment sheet with the recurring details + pub label: String, + /// The date of the first payment + #[schema(example = "2023-09-10T23:59:59Z")] + #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + pub recurring_payment_start_date: Option, + /// The date of the final payment + #[schema(example = "2023-09-10T23:59:59Z")] + #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + pub recurring_payment_end_date: Option, + /// The amount of time — in calendar units, such as day, month, or year — that represents a fraction of the total payment interval + pub recurring_payment_interval_unit: Option, + /// The number of interval units that make up the total payment interval + pub recurring_payment_interval_count: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum RecurringPaymentIntervalUnit { + Year, + Month, + Day, + Hour, + Minute, } ///frm message is an object sent inside the payments response...when frm is invoked, its value is Some(...), else its None @@ -5654,6 +6681,85 @@ pub struct FrmMessage { pub frm_error: Option, } +#[cfg(feature = "v2")] +mod payment_id_type { + use std::{borrow::Cow, fmt}; + + use serde::{ + de::{self, Visitor}, + Deserializer, + }; + + use super::PaymentIdType; + + struct PaymentIdVisitor; + struct OptionalPaymentIdVisitor; + + impl Visitor<'_> for PaymentIdVisitor { + type Value = PaymentIdType; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("payment id") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + common_utils::id_type::GlobalPaymentId::try_from(Cow::Owned(value.to_string())) + .map_err(de::Error::custom) + .map(PaymentIdType::PaymentIntentId) + } + } + + impl<'de> Visitor<'de> for OptionalPaymentIdVisitor { + type Value = Option; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("payment id") + } + + fn visit_some(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_any(PaymentIdVisitor).map(Some) + } + + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } + + #[allow(dead_code)] + pub(crate) fn deserialize<'a, D>(deserializer: D) -> Result + where + D: Deserializer<'a>, + { + deserializer.deserialize_any(PaymentIdVisitor) + } + + pub(crate) fn deserialize_option<'a, D>( + deserializer: D, + ) -> Result, D::Error> + where + D: Deserializer<'a>, + { + deserializer.deserialize_option(OptionalPaymentIdVisitor) + } +} + +#[cfg(feature = "v1")] mod payment_id_type { use std::{borrow::Cow, fmt}; @@ -5667,7 +6773,7 @@ mod payment_id_type { struct PaymentIdVisitor; struct OptionalPaymentIdVisitor; - impl<'de> Visitor<'de> for PaymentIdVisitor { + impl Visitor<'_> for PaymentIdVisitor { type Value = PaymentIdType; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -5741,7 +6847,7 @@ pub mod amount { // This is defined to provide guarded deserialization of amount // which itself handles zero and non-zero values internally - impl<'de> de::Visitor<'de> for AmountVisitor { + impl de::Visitor<'_> for AmountVisitor { type Value = Amount; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -5903,15 +7009,24 @@ pub struct PaymentLinkDetails { pub merchant_description: Option, pub sdk_layout: String, pub display_sdk_only: bool, + pub hide_card_nickname_field: bool, + pub show_card_form_by_default: bool, pub locale: Option, pub transaction_details: Option>, + pub background_image: Option, + pub details_layout: Option, + pub branding_visibility: Option, + pub payment_button_text: Option, } #[derive(Debug, serde::Serialize, Clone)] pub struct SecurePaymentLinkDetails { pub enabled_saved_payment_method: bool, + pub hide_card_nickname_field: bool, + pub show_card_form_by_default: bool, #[serde(flatten)] pub payment_link_details: PaymentLinkDetails, + pub payment_button_text: Option, } #[derive(Debug, serde::Serialize)] @@ -5937,7 +7052,6 @@ pub struct PaymentLinkStatusDetails { #[derive(Clone, Debug, serde::Deserialize, ToSchema, serde::Serialize)] #[serde(deny_unknown_fields)] - pub struct PaymentLinkListConstraints { /// limit on the number of objects to return pub limit: Option, @@ -6034,6 +7148,28 @@ pub struct ExtendedCardInfoResponse { pub payload: String, } +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] +pub struct ClickToPaySessionResponse { + pub dpa_id: String, + pub dpa_name: String, + pub locale: String, + pub card_brands: Vec, + pub acquirer_bin: String, + pub acquirer_merchant_id: String, + pub merchant_category_code: String, + pub merchant_country_code: String, + #[schema(value_type = String, example = "38.02")] + pub transaction_amount: StringMajorUnit, + #[schema(value_type = Currency)] + pub transaction_currency_code: common_enums::Currency, + #[schema(value_type = Option, max_length = 255, example = "9123456789")] + pub phone_number: Option>, + #[schema(max_length = 255, value_type = Option, example = "johntest@test.com")] + pub email: Option, + pub phone_country_code: Option, +} + +#[cfg(feature = "v1")] #[cfg(test)] mod payments_request_api_contract { #![allow(clippy::unwrap_used)] diff --git a/crates/api_models/src/payouts.rs b/crates/api_models/src/payouts.rs index a7be9703d380..237d165d572c 100644 --- a/crates/api_models/src/payouts.rs +++ b/crates/api_models/src/payouts.rs @@ -19,7 +19,7 @@ use crate::{enums as api_enums, payment_methods::RequiredFieldInfo, payments}; #[derive(Debug, Deserialize, Serialize, Clone, ToSchema)] pub enum PayoutRequest { PayoutActionRequest(PayoutActionRequest), - PayoutCreateRequest(PayoutCreateRequest), + PayoutCreateRequest(Box), PayoutRetrieveRequest(PayoutRetrieveRequest), } diff --git a/crates/api_models/src/pm_auth.rs b/crates/api_models/src/pm_auth.rs index 443a4c303945..e97efcf92d3d 100644 --- a/crates/api_models/src/pm_auth.rs +++ b/crates/api_models/src/pm_auth.rs @@ -4,8 +4,6 @@ use common_utils::{ id_type, impl_api_event_type, }; -use crate::enums as api_enums; - #[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "snake_case")] pub struct LinkTokenCreateRequest { @@ -14,9 +12,6 @@ pub struct LinkTokenCreateRequest { pub payment_id: id_type::PaymentId, // payment_id to be passed in req body for redis pm_auth connector name fetch pub payment_method: PaymentMethod, // payment_method to be used for filtering pm_auth connector pub payment_method_type: PaymentMethodType, // payment_method_type to be used for filtering pm_auth connector - pub client_platform: api_enums::ClientPlatform, // Client Platform to perform platform based processing - pub android_package_name: Option, // Android Package name to be sent for Android platform - pub redirect_uri: Option, // Merchant redirect_uri to be sent in case of IOS platform } #[derive(Debug, Clone, serde::Serialize)] @@ -27,7 +22,6 @@ pub struct LinkTokenCreateResponse { #[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "snake_case")] - pub struct ExchangeTokenCreateRequest { pub public_token: String, pub client_secret: Option, diff --git a/crates/api_models/src/recon.rs b/crates/api_models/src/recon.rs index afee0fb5626a..f73bcc5ae1f1 100644 --- a/crates/api_models/src/recon.rs +++ b/crates/api_models/src/recon.rs @@ -1,4 +1,4 @@ -use common_utils::pii; +use common_utils::{id_type, pii}; use masking::Secret; use crate::enums; @@ -18,3 +18,11 @@ pub struct ReconTokenResponse { pub struct ReconStatusResponse { pub recon_status: enums::ReconStatus, } + +#[derive(serde::Serialize, Debug)] +pub struct VerifyTokenResponse { + pub merchant_id: id_type::MerchantId, + pub user_email: pii::Email, + #[serde(skip_serializing_if = "Option::is_none")] + pub acl: Option, +} diff --git a/crates/api_models/src/refunds.rs b/crates/api_models/src/refunds.rs index 237a903d80c0..36a92e3ab03d 100644 --- a/crates/api_models/src/refunds.rs +++ b/crates/api_models/src/refunds.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -pub use common_utils::types::{ChargeRefunds, MinorUnit}; +pub use common_utils::types::MinorUnit; use common_utils::{pii, types::TimeRange}; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -57,8 +57,47 @@ pub struct RefundRequest { 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, + #[schema(value_type = Option)] + pub split_refunds: Option, +} + +#[cfg(feature = "v2")] +#[derive(Debug, ToSchema, Clone, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct RefundsCreateRequest { + /// The payment id against which refund is initiated + #[schema( + max_length = 30, + min_length = 30, + example = "pay_mbabizu24mvu3mela5njyhpit4", + value_type = String, + )] + pub payment_id: common_utils::id_type::GlobalPaymentId, + + /// Unique Identifier for the Refund. This is to ensure idempotency for multiple partial refunds initiated against the same payment. + #[schema( + max_length = 30, + min_length = 30, + example = "ref_mbabizu24mvu3mela5njyhpit4", + value_type = Option, + )] + pub merchant_reference_id: Option, + + /// Total amount for which the refund is to be initiated. Amount for the payment in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc., If not provided, this will default to the amount_captured of the payment + #[schema(value_type = Option , minimum = 100, example = 6540)] + pub amount: Option, + + /// Reason for the refund. Often useful for displaying to users and your customer support executive. + #[schema(max_length = 255, example = "Customer returned the product")] + pub reason: Option, + + /// To indicate whether to refund needs to be instant or scheduled. Default value is instant + #[schema(default = "Instant", example = "Instant")] + pub refund_type: Option, + + /// Metadata is useful for storing additional, unstructured information on an object. + #[schema(value_type = Option, example = r#"{ "city": "NY", "unit": "245" }"#)] + pub metadata: Option, } #[derive(Default, Debug, Clone, Deserialize)] @@ -125,6 +164,7 @@ pub enum RefundType { Instant, } +#[cfg(feature = "v1")] #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, ToSchema)] pub struct RefundResponse { /// Unique Identifier for the refund @@ -164,8 +204,80 @@ pub struct RefundResponse { #[schema(value_type = Option)] 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, + #[schema(value_type = Option,)] + pub split_refunds: Option, +} + +#[cfg(feature = "v1")] +impl RefundResponse { + pub fn get_refund_id_as_string(&self) -> String { + self.refund_id.clone() + } +} + +#[cfg(feature = "v2")] +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, ToSchema)] +pub struct RefundResponse { + /// Global Refund Id for the refund + #[schema(value_type = String)] + pub id: common_utils::id_type::GlobalRefundId, + /// The payment id against which refund is initiated + #[schema(value_type = String)] + pub payment_id: common_utils::id_type::GlobalPaymentId, + /// Unique Identifier for the Refund. This is to ensure idempotency for multiple partial refunds initiated against the same payment. + #[schema( + max_length = 30, + min_length = 30, + example = "ref_mbabizu24mvu3mela5njyhpit4", + value_type = Option, + )] + pub merchant_reference_id: Option, + /// The refund amount + #[schema(value_type = i64 , minimum = 100, example = 6540)] + pub amount: MinorUnit, + /// The three-letter ISO currency code + #[schema(value_type = Currency)] + pub currency: common_enums::Currency, + /// The status for refund + pub status: RefundStatus, + /// An arbitrary string attached to the object + pub reason: Option, + /// Metadata is useful for storing additional, unstructured information on an object + #[schema(value_type = Option)] + pub metadata: Option, + /// The error details for the refund + pub error_details: Option, + /// The timestamp at which refund is created + #[serde(with = "common_utils::custom_serde::iso8601")] + pub created_at: PrimitiveDateTime, + /// The timestamp at which refund is updated + #[serde(with = "common_utils::custom_serde::iso8601")] + pub updated_at: PrimitiveDateTime, + /// The connector used for the refund and the corresponding payment + #[schema(example = "stripe", value_type = Connector)] + pub connector: enums::Connector, + /// The id of business profile for this refund + #[schema(value_type = String)] + pub profile_id: common_utils::id_type::ProfileId, + /// The merchant_connector_id of the processor through which this payment went through + #[schema(value_type = String)] + pub merchant_connector_id: common_utils::id_type::MerchantConnectorAccountId, + /// The reference id of the connector for the refund + pub connector_refund_reference_id: Option, +} + +#[cfg(feature = "v2")] +impl RefundResponse { + pub fn get_refund_id_as_string(&self) -> String { + self.id.get_string_repr().to_owned() + } +} + +#[cfg(feature = "v2")] +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, ToSchema)] +pub struct RefundErrorDetails { + pub code: String, + pub message: String, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, ToSchema)] @@ -234,7 +346,7 @@ pub struct RefundListFilters { pub refund_status: Vec, } -#[derive(Clone, Debug, serde::Serialize, ToSchema)] +#[derive(Clone, Debug, serde::Serialize)] pub struct RefundAggregateResponse { /// The list of refund status with their count pub status_with_count: HashMap, diff --git a/crates/api_models/src/relay.rs b/crates/api_models/src/relay.rs new file mode 100644 index 000000000000..f54e14716327 --- /dev/null +++ b/crates/api_models/src/relay.rs @@ -0,0 +1,103 @@ +use common_utils::types::MinorUnit; +use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; + +use crate::enums as api_enums; + +#[derive(Debug, ToSchema, Clone, Deserialize, Serialize)] +pub struct RelayRequest { + /// The identifier that is associated to a resource at the connector reference to which the relay request is being made + #[schema(example = "7256228702616471803954")] + pub connector_resource_id: String, + /// Identifier of the connector ( merchant connector account ) which was chosen to make the payment + #[schema(example = "mca_5apGeP94tMts6rg3U3kR", value_type = String)] + pub connector_id: common_utils::id_type::MerchantConnectorAccountId, + /// The type of relay request + #[serde(rename = "type")] + #[schema(value_type = RelayType)] + pub relay_type: api_enums::RelayType, + /// The data that is associated with the relay request + pub data: Option, +} + +#[derive(Debug, ToSchema, Clone, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum RelayData { + /// The data that is associated with a refund relay request + Refund(RelayRefundRequest), +} + +#[derive(Debug, ToSchema, Clone, Deserialize, Serialize)] +pub struct RelayRefundRequest { + /// The amount that is being refunded + #[schema(value_type = i64 , example = 6540)] + pub amount: MinorUnit, + /// The currency in which the amount is being refunded + #[schema(value_type = Currency)] + pub currency: api_enums::Currency, + /// The reason for the refund + #[schema(max_length = 255, example = "Customer returned the product")] + pub reason: Option, +} + +#[derive(Debug, ToSchema, Clone, Deserialize, Serialize)] +pub struct RelayResponse { + /// The unique identifier for the Relay + #[schema(example = "relay_mbabizu24mvu3mela5njyhpit4", value_type = String)] + pub id: common_utils::id_type::RelayId, + /// The status of the relay request + #[schema(value_type = RelayStatus)] + pub status: api_enums::RelayStatus, + /// The identifier that is associated to a resource at the connector reference to which the relay request is being made + #[schema(example = "pi_3MKEivSFNglxLpam0ZaL98q9")] + pub connector_resource_id: String, + /// The error details if the relay request failed + pub error: Option, + /// The identifier that is associated to a resource at the connector to which the relay request is being made + #[schema(example = "re_3QY4TnEOqOywnAIx1Mm1p7GQ")] + pub connector_reference_id: Option, + /// Identifier of the connector ( merchant connector account ) which was chosen to make the payment + #[schema(example = "mca_5apGeP94tMts6rg3U3kR", value_type = String)] + pub connector_id: common_utils::id_type::MerchantConnectorAccountId, + /// The business profile that is associated with this relay request. + #[schema(example = "pro_abcdefghijklmnopqrstuvwxyz", value_type = String)] + pub profile_id: common_utils::id_type::ProfileId, + /// The type of relay request + #[serde(rename = "type")] + #[schema(value_type = RelayType)] + pub relay_type: api_enums::RelayType, + /// The data that is associated with the relay request + pub data: Option, +} + +#[derive(Debug, ToSchema, Clone, Deserialize, Serialize)] +pub struct RelayError { + /// The error code + pub code: String, + /// The error message + pub message: String, +} + +#[derive(Debug, ToSchema, Clone, Deserialize, Serialize)] +pub struct RelayRetrieveRequest { + /// The unique identifier for the Relay + #[serde(default)] + pub force_sync: bool, + /// The unique identifier for the Relay + pub id: common_utils::id_type::RelayId, +} + +#[derive(Debug, ToSchema, Clone, Deserialize, Serialize)] +pub struct RelayRetrieveBody { + /// The unique identifier for the Relay + #[serde(default)] + pub force_sync: bool, +} + +impl common_utils::events::ApiEventMetric for RelayRequest {} + +impl common_utils::events::ApiEventMetric for RelayResponse {} + +impl common_utils::events::ApiEventMetric for RelayRetrieveRequest {} + +impl common_utils::events::ApiEventMetric for RelayRetrieveBody {} diff --git a/crates/api_models/src/routing.rs b/crates/api_models/src/routing.rs index fc65ab037c2b..2e570816ab4c 100644 --- a/crates/api_models/src/routing.rs +++ b/crates/api_models/src/routing.rs @@ -473,7 +473,6 @@ impl RoutingAlgorithmRef { } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] - pub struct RoutingDictionaryRecord { #[schema(value_type = String)] pub id: common_utils::id_type::RoutingId, @@ -522,22 +521,200 @@ pub struct DynamicAlgorithmWithTimestamp { #[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)] pub struct DynamicRoutingAlgorithmRef { - pub success_based_algorithm: - Option>, + pub success_based_algorithm: Option, + pub dynamic_routing_volume_split: Option, + pub elimination_routing_algorithm: Option, +} + +pub trait DynamicRoutingAlgoAccessor { + fn get_algorithm_id_with_timestamp( + self, + ) -> DynamicAlgorithmWithTimestamp; + fn get_enabled_features(&mut self) -> &mut DynamicRoutingFeatures; +} + +impl DynamicRoutingAlgoAccessor for SuccessBasedAlgorithm { + fn get_algorithm_id_with_timestamp( + self, + ) -> DynamicAlgorithmWithTimestamp { + self.algorithm_id_with_timestamp + } + fn get_enabled_features(&mut self) -> &mut DynamicRoutingFeatures { + &mut self.enabled_feature + } +} + +impl DynamicRoutingAlgoAccessor for EliminationRoutingAlgorithm { + fn get_algorithm_id_with_timestamp( + self, + ) -> DynamicAlgorithmWithTimestamp { + self.algorithm_id_with_timestamp + } + fn get_enabled_features(&mut self) -> &mut DynamicRoutingFeatures { + &mut self.enabled_feature + } } impl DynamicRoutingAlgorithmRef { - pub fn update_algorithm_id(&mut self, new_id: common_utils::id_type::RoutingId) { - self.success_based_algorithm = Some(DynamicAlgorithmWithTimestamp { - algorithm_id: Some(new_id), - timestamp: common_utils::date_time::now_unix_timestamp(), - }) + pub fn update(&mut self, new: Self) { + if let Some(elimination_routing_algorithm) = new.elimination_routing_algorithm { + self.elimination_routing_algorithm = Some(elimination_routing_algorithm) + } + if let Some(success_based_algorithm) = new.success_based_algorithm { + self.success_based_algorithm = Some(success_based_algorithm) + } + } + + pub fn update_specific_ref( + &mut self, + algo_type: DynamicRoutingType, + feature_to_enable: DynamicRoutingFeatures, + ) { + match algo_type { + DynamicRoutingType::SuccessRateBasedRouting => { + self.success_based_algorithm + .as_mut() + .map(|algo| algo.enabled_feature = feature_to_enable); + } + DynamicRoutingType::EliminationRouting => { + self.elimination_routing_algorithm + .as_mut() + .map(|algo| algo.enabled_feature = feature_to_enable); + } + } + } + + pub fn update_volume_split(&mut self, volume: Option) { + self.dynamic_routing_volume_split = volume + } +} + +impl EliminationRoutingAlgorithm { + pub fn new( + algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp< + common_utils::id_type::RoutingId, + >, + ) -> Self { + Self { + algorithm_id_with_timestamp, + enabled_feature: DynamicRoutingFeatures::None, + } + } +} + +impl SuccessBasedAlgorithm { + pub fn new( + algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp< + common_utils::id_type::RoutingId, + >, + ) -> Self { + Self { + algorithm_id_with_timestamp, + enabled_feature: DynamicRoutingFeatures::None, + } + } +} + +#[derive(Debug, Default, Clone, Copy, serde::Serialize, serde::Deserialize)] +pub struct RoutingVolumeSplit { + pub routing_type: RoutingType, + pub split: u8, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct RoutingVolumeSplitWrapper { + pub routing_info: RoutingVolumeSplit, + pub profile_id: common_utils::id_type::ProfileId, +} + +#[derive(Debug, Default, Clone, Copy, serde::Serialize, serde::Deserialize, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +pub enum RoutingType { + #[default] + Static, + Dynamic, +} + +impl RoutingType { + pub fn is_dynamic_routing(self) -> bool { + self == Self::Dynamic + } +} +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct SuccessBasedAlgorithm { + pub algorithm_id_with_timestamp: + DynamicAlgorithmWithTimestamp, + #[serde(default)] + pub enabled_feature: DynamicRoutingFeatures, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct EliminationRoutingAlgorithm { + pub algorithm_id_with_timestamp: + DynamicAlgorithmWithTimestamp, + #[serde(default)] + pub enabled_feature: DynamicRoutingFeatures, +} + +impl EliminationRoutingAlgorithm { + pub fn update_enabled_features(&mut self, feature_to_enable: DynamicRoutingFeatures) { + self.enabled_feature = feature_to_enable + } +} + +impl SuccessBasedAlgorithm { + pub fn update_enabled_features(&mut self, feature_to_enable: DynamicRoutingFeatures) { + self.enabled_feature = feature_to_enable + } +} + +impl DynamicRoutingAlgorithmRef { + pub fn update_algorithm_id( + &mut self, + new_id: common_utils::id_type::RoutingId, + enabled_feature: DynamicRoutingFeatures, + dynamic_routing_type: DynamicRoutingType, + ) { + match dynamic_routing_type { + DynamicRoutingType::SuccessRateBasedRouting => { + self.success_based_algorithm = Some(SuccessBasedAlgorithm { + algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp { + algorithm_id: Some(new_id), + timestamp: common_utils::date_time::now_unix_timestamp(), + }, + enabled_feature, + }) + } + DynamicRoutingType::EliminationRouting => { + self.elimination_routing_algorithm = Some(EliminationRoutingAlgorithm { + algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp { + algorithm_id: Some(new_id), + timestamp: common_utils::date_time::now_unix_timestamp(), + }, + enabled_feature, + }) + } + }; } } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] -pub struct ToggleSuccessBasedRoutingQuery { - pub status: bool, +pub struct ToggleDynamicRoutingQuery { + pub enable: DynamicRoutingFeatures, +} + +#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct DynamicRoutingVolumeSplitQuery { + pub split: u8, +} + +#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum DynamicRoutingFeatures { + Metrics, + DynamicConnectorSelection, + #[default] + None, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] @@ -549,26 +726,51 @@ pub struct SuccessBasedRoutingUpdateConfigQuery { } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] -pub struct ToggleSuccessBasedRoutingWrapper { +pub struct ToggleDynamicRoutingWrapper { pub profile_id: common_utils::id_type::ProfileId, - pub status: bool, + pub feature_to_enable: DynamicRoutingFeatures, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] -pub struct ToggleSuccessBasedRoutingPath { +pub struct ToggleDynamicRoutingPath { #[schema(value_type = String)] pub profile_id: common_utils::id_type::ProfileId, } + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)] +pub struct EliminationRoutingConfig { + pub params: Option>, + pub elimination_analyser_config: Option, +} + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)] +pub struct EliminationAnalyserConfig { + pub bucket_size: Option, + pub bucket_leak_interval_in_secs: Option, +} + +impl Default for EliminationRoutingConfig { + fn default() -> Self { + Self { + params: Some(vec![DynamicRoutingConfigParams::PaymentMethod]), + elimination_analyser_config: Some(EliminationAnalyserConfig { + bucket_size: Some(5), + bucket_leak_interval_in_secs: Some(2), + }), + } + } +} + #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)] pub struct SuccessBasedRoutingConfig { - pub params: Option>, + pub params: Option>, pub config: Option, } impl Default for SuccessBasedRoutingConfig { fn default() -> Self { Self { - params: Some(vec![SuccessBasedRoutingConfigParams::PaymentMethod]), + params: Some(vec![DynamicRoutingConfigParams::PaymentMethod]), config: Some(SuccessBasedRoutingConfigBody { min_aggregates_size: Some(2), default_success_rate: Some(100.0), @@ -583,11 +785,14 @@ impl Default for SuccessBasedRoutingConfig { } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema, strum::Display)] -pub enum SuccessBasedRoutingConfigParams { +pub enum DynamicRoutingConfigParams { PaymentMethod, PaymentMethodType, - Currency, AuthenticationType, + Currency, + Country, + CardNetwork, + CardBin, } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)] @@ -611,6 +816,12 @@ pub struct SuccessBasedRoutingPayloadWrapper { pub profile_id: common_utils::id_type::ProfileId, } +#[derive(Debug, Clone, strum::Display, serde::Serialize, serde::Deserialize)] +pub enum DynamicRoutingType { + SuccessRateBasedRouting, + EliminationRouting, +} + impl SuccessBasedRoutingConfig { pub fn update(&mut self, new: Self) { if let Some(params) = new.params { @@ -648,3 +859,18 @@ impl CurrentBlockThreshold { } } } + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +pub struct RoutableConnectorChoiceWithBucketName { + pub routable_connector_choice: RoutableConnectorChoice, + pub bucket_name: String, +} + +impl RoutableConnectorChoiceWithBucketName { + pub fn new(routable_connector_choice: RoutableConnectorChoice, bucket_name: String) -> Self { + Self { + routable_connector_choice, + bucket_name, + } + } +} diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs index 7b22387b3c30..7b5911cf1a84 100644 --- a/crates/api_models/src/user.rs +++ b/crates/api_models/src/user.rs @@ -1,6 +1,6 @@ use std::fmt::Debug; -use common_enums::{EntityType, PermissionGroup, RoleScope, TokenPurpose}; +use common_enums::{EntityType, TokenPurpose}; use common_utils::{crypto::OptionalEncryptableName, id_type, pii}; use masking::Secret; @@ -8,6 +8,8 @@ use crate::user_role::UserStatus; pub mod dashboard_metadata; #[cfg(feature = "dummy_connector")] pub mod sample_data; +#[cfg(feature = "control_center_theme")] +pub mod theme; #[derive(serde::Deserialize, Debug, Clone, serde::Serialize)] pub struct SignUpWithMerchantIdRequest { @@ -25,19 +27,6 @@ pub struct SignUpRequest { pub password: Secret, } -#[derive(serde::Serialize, Debug, Clone)] -pub struct DashboardEntryResponse { - pub token: Secret, - pub merchant_id: id_type::MerchantId, - pub name: Secret, - pub email: pii::Email, - pub verification_days_left: Option, - pub user_role: String, - //this field is added for audit/debug reasons - #[serde(skip_serializing)] - pub user_id: String, -} - pub type SignInRequest = SignUpRequest; #[derive(serde::Deserialize, Debug, Clone, serde::Serialize)] @@ -126,23 +115,24 @@ pub struct CreateInternalUserRequest { pub password: Secret, } -#[derive(Debug, serde::Deserialize, serde::Serialize)] -pub struct UserMerchantCreate { - pub company_name: String, +#[derive(serde::Deserialize, Debug, serde::Serialize)] +pub struct CreateTenantUserRequest { + pub name: Secret, + pub email: pii::Email, + pub password: Secret, } -#[derive(Debug, serde::Serialize)] -pub struct ListUsersResponse(pub Vec); +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +pub struct UserOrgMerchantCreateRequest { + pub organization_name: Secret, + pub organization_details: Option, + pub metadata: Option, + pub merchant_name: Secret, +} -#[derive(Debug, serde::Serialize)] -pub struct UserDetails { - pub email: pii::Email, - pub name: Secret, - pub role_id: String, - pub role_name: String, - pub status: UserStatus, - #[serde(with = "common_utils::custom_serde::iso8601")] - pub last_modified_at: time::PrimitiveDateTime, +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct UserMerchantCreate { + pub company_name: String, } #[derive(serde::Serialize, Debug, Clone)] @@ -160,6 +150,7 @@ pub struct GetUserDetailsResponse { pub recovery_codes_left: Option, pub profile_id: id_type::ProfileId, pub entity_type: EntityType, + pub theme_id: Option, } #[derive(Debug, serde::Deserialize, serde::Serialize)] @@ -167,19 +158,6 @@ pub struct GetUserRoleDetailsRequest { pub email: pii::Email, } -#[derive(Debug, serde::Serialize)] -pub struct GetUserRoleDetailsResponse { - pub email: pii::Email, - pub name: Secret, - pub role_id: String, - pub role_name: String, - pub status: UserStatus, - #[serde(with = "common_utils::custom_serde::iso8601")] - pub last_modified_at: time::PrimitiveDateTime, - pub groups: Vec, - pub role_scope: RoleScope, -} - #[derive(Debug, serde::Serialize)] pub struct GetUserRoleDetailsResponseV2 { pub role_id: String, @@ -207,23 +185,6 @@ pub struct SendVerifyEmailRequest { pub email: pii::Email, } -#[derive(Debug, serde::Serialize)] -pub struct UserMerchantAccount { - pub merchant_id: id_type::MerchantId, - pub merchant_name: OptionalEncryptableName, - pub is_active: bool, - pub role_id: String, - pub role_name: String, - pub org_id: id_type::OrganizationId, -} - -#[cfg(feature = "recon")] -#[derive(serde::Serialize, Debug)] -pub struct VerifyTokenResponse { - pub merchant_id: id_type::MerchantId, - pub user_email: pii::Email, -} - #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct UpdateUserAccountDetailsRequest { pub name: Option>, @@ -246,6 +207,24 @@ pub struct TwoFactorAuthStatusResponse { pub recovery_code: bool, } +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct TwoFactorAuthAttempts { + pub is_completed: bool, + pub remaining_attempts: u8, +} + +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct TwoFactorAuthStatusResponseWithAttempts { + pub totp: TwoFactorAuthAttempts, + pub recovery_code: TwoFactorAuthAttempts, +} + +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct TwoFactorStatus { + pub status: Option, + pub is_skippable: bool, +} + #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct UserFromEmailRequest { pub token: Secret, @@ -367,8 +346,9 @@ pub struct SsoSignInRequest { } #[derive(Debug, serde::Deserialize, serde::Serialize)] -pub struct AuthIdQueryParam { +pub struct AuthIdAndThemeIdQueryParam { pub auth_id: Option, + pub theme_id: Option, } #[derive(Debug, serde::Deserialize, serde::Serialize)] diff --git a/crates/api_models/src/user/sample_data.rs b/crates/api_models/src/user/sample_data.rs index dc95de913faf..bfcbcb046c53 100644 --- a/crates/api_models/src/user/sample_data.rs +++ b/crates/api_models/src/user/sample_data.rs @@ -1,5 +1,4 @@ use common_enums::{AuthenticationType, CountryAlpha2}; -use common_utils::{self}; use time::PrimitiveDateTime; use crate::enums::Connector; diff --git a/crates/api_models/src/user/theme.rs b/crates/api_models/src/user/theme.rs new file mode 100644 index 000000000000..c79da307b9a8 --- /dev/null +++ b/crates/api_models/src/user/theme.rs @@ -0,0 +1,131 @@ +use actix_multipart::form::{bytes::Bytes, text::Text, MultipartForm}; +use common_enums::EntityType; +use common_utils::{ + id_type, + types::theme::{EmailThemeConfig, ThemeLineage}, +}; +use masking::Secret; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize)] +pub struct GetThemeResponse { + pub theme_id: String, + pub theme_name: String, + pub entity_type: EntityType, + pub tenant_id: id_type::TenantId, + pub org_id: Option, + pub merchant_id: Option, + pub profile_id: Option, + pub email_config: EmailThemeConfig, + pub theme_data: ThemeData, +} + +#[derive(Debug, MultipartForm)] +pub struct UploadFileAssetData { + pub asset_name: Text, + #[multipart(limit = "10MB")] + pub asset_data: Bytes, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct UploadFileRequest { + pub lineage: ThemeLineage, + pub asset_name: String, + pub asset_data: Secret>, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct CreateThemeRequest { + pub lineage: ThemeLineage, + pub theme_name: String, + pub theme_data: ThemeData, + pub email_config: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct UpdateThemeRequest { + pub lineage: ThemeLineage, + pub theme_data: ThemeData, + // TODO: Add support to update email config +} + +// All the below structs are for the theme.json file, +// which will be used by frontend to style the dashboard. +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ThemeData { + settings: Settings, + urls: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct Settings { + colors: Colors, + typography: Option, + buttons: Buttons, + borders: Option, + spacing: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct Colors { + primary: String, + sidebar: String, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct Typography { + font_family: Option, + font_size: Option, + heading_font_size: Option, + text_color: Option, + link_color: Option, + link_hover_color: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct Buttons { + primary: PrimaryButton, + secondary: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct PrimaryButton { + background_color: Option, + text_color: Option, + hover_background_color: String, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct SecondaryButton { + background_color: Option, + text_color: Option, + hover_background_color: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct Borders { + default_radius: Option, + border_color: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct Spacing { + padding: Option, + margin: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +struct Urls { + favicon_url: Option, + logo_url: Option, +} diff --git a/crates/api_models/src/user_role.rs b/crates/api_models/src/user_role.rs index f2743d6a3116..19027c3cbf86 100644 --- a/crates/api_models/src/user_role.rs +++ b/crates/api_models/src/user_role.rs @@ -1,100 +1,24 @@ -use common_enums::PermissionGroup; +use common_enums::{ParentGroup, PermissionGroup}; use common_utils::pii; use masking::Secret; pub mod role; -#[derive(Debug, serde::Serialize)] -pub enum Permission { - PaymentRead, - PaymentWrite, - RefundRead, - RefundWrite, - ApiKeyRead, - ApiKeyWrite, - MerchantAccountRead, - MerchantAccountWrite, - MerchantConnectorAccountRead, - MerchantConnectorAccountWrite, - RoutingRead, - RoutingWrite, - DisputeRead, - DisputeWrite, - MandateRead, - MandateWrite, - CustomerRead, - CustomerWrite, - Analytics, - ThreeDsDecisionManagerWrite, - ThreeDsDecisionManagerRead, - SurchargeDecisionManagerWrite, - SurchargeDecisionManagerRead, - UsersRead, - UsersWrite, - MerchantAccountCreate, - WebhookEventRead, - PayoutWrite, - PayoutRead, - WebhookEventWrite, - GenerateReport, - ReconAdmin, -} - -#[derive(Clone, Debug, serde::Serialize, PartialEq, Eq, Hash)] -pub enum ParentGroup { - Operations, - Connectors, - Workflows, - Analytics, - Users, - #[serde(rename = "MerchantAccess")] - Merchant, - #[serde(rename = "OrganizationAccess")] - Organization, - Recon, -} - -#[derive(Debug, serde::Serialize)] -pub enum PermissionModule { - Payments, - Refunds, - MerchantAccount, - Connectors, - Routing, - Analytics, - Mandates, - Customer, - Disputes, - ThreeDsDecisionManager, - SurchargeDecisionManager, - AccountCreate, - Payouts, - Recon, -} - #[derive(Debug, serde::Serialize)] pub struct AuthorizationInfoResponse(pub Vec); #[derive(Debug, serde::Serialize)] #[serde(untagged)] pub enum AuthorizationInfo { - Module(ModuleInfo), Group(GroupInfo), GroupWithTag(ParentInfo), } -#[derive(Debug, serde::Serialize)] -pub struct ModuleInfo { - pub module: PermissionModule, - pub description: &'static str, - pub permissions: Vec, -} - +// TODO: To be deprecated #[derive(Debug, serde::Serialize)] pub struct GroupInfo { pub group: PermissionGroup, pub description: &'static str, - pub permissions: Vec, } #[derive(Debug, serde::Serialize, Clone)] @@ -104,12 +28,6 @@ pub struct ParentInfo { pub groups: Vec, } -#[derive(Debug, serde::Serialize)] -pub struct PermissionInfo { - pub enum_name: Permission, - pub description: &'static str, -} - #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct UpdateUserRoleRequest { pub email: pii::Email, @@ -122,16 +40,6 @@ pub enum UserStatus { InvitationSent, } -#[derive(Debug, serde::Deserialize, serde::Serialize)] -pub struct MerchantSelectRequest { - pub merchant_ids: Vec, -} - -#[derive(Debug, serde::Deserialize, serde::Serialize)] -pub struct AcceptInvitationRequest { - pub merchant_ids: Vec, -} - #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct DeleteUserRoleRequest { pub email: pii::Email, diff --git a/crates/api_models/src/user_role/role.rs b/crates/api_models/src/user_role/role.rs index be467421e650..7c877cd74777 100644 --- a/crates/api_models/src/user_role/role.rs +++ b/crates/api_models/src/user_role/role.rs @@ -1,7 +1,6 @@ -pub use common_enums::PermissionGroup; -use common_enums::{EntityType, RoleScope}; - -use super::Permission; +use common_enums::{ + EntityType, ParentGroup, PermissionGroup, PermissionScope, Resource, RoleScope, +}; #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct CreateRoleRequest { @@ -17,24 +16,28 @@ pub struct UpdateRoleRequest { } #[derive(Debug, serde::Serialize)] -pub struct ListRolesResponse(pub Vec); - -#[derive(Debug, serde::Serialize)] -pub struct RoleInfoWithPermissionsResponse { +pub struct RoleInfoWithGroupsResponse { pub role_id: String, - pub permissions: Vec, + pub groups: Vec, pub role_name: String, pub role_scope: RoleScope, } #[derive(Debug, serde::Serialize)] -pub struct RoleInfoWithGroupsResponse { +pub struct RoleInfoWithParents { pub role_id: String, - pub groups: Vec, + pub parent_groups: Vec, pub role_name: String, pub role_scope: RoleScope, } +#[derive(Debug, serde::Serialize)] +pub struct ParentGroupInfo { + pub name: ParentGroup, + pub description: String, + pub scopes: Vec, +} + #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct ListRolesRequest { pub entity_type: Option, @@ -70,3 +73,9 @@ pub struct MinimalRoleInfo { pub role_id: String, pub role_name: String, } + +#[derive(Debug, serde::Serialize)] +pub struct GroupsAndResources { + pub groups: Vec, + pub resources: Vec, +} diff --git a/crates/api_models/src/webhooks.rs b/crates/api_models/src/webhooks.rs index ce9b303066ed..e6f4065eb7a9 100644 --- a/crates/api_models/src/webhooks.rs +++ b/crates/api_models/src/webhooks.rs @@ -76,25 +76,45 @@ pub enum WebhookFlow { #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] /// This enum tells about the affect a webhook had on an object pub enum WebhookResponseTracker { + #[cfg(feature = "v1")] Payment { payment_id: common_utils::id_type::PaymentId, status: common_enums::IntentStatus, }, + #[cfg(feature = "v2")] + Payment { + payment_id: common_utils::id_type::GlobalPaymentId, + status: common_enums::IntentStatus, + }, #[cfg(feature = "payouts")] Payout { payout_id: String, status: common_enums::PayoutStatus, }, + #[cfg(feature = "v1")] Refund { payment_id: common_utils::id_type::PaymentId, refund_id: String, status: common_enums::RefundStatus, }, + #[cfg(feature = "v2")] + Refund { + payment_id: common_utils::id_type::GlobalPaymentId, + refund_id: String, + status: common_enums::RefundStatus, + }, + #[cfg(feature = "v1")] Dispute { dispute_id: String, payment_id: common_utils::id_type::PaymentId, status: common_enums::DisputeStatus, }, + #[cfg(feature = "v2")] + Dispute { + dispute_id: String, + payment_id: common_utils::id_type::GlobalPaymentId, + status: common_enums::DisputeStatus, + }, Mandate { mandate_id: String, status: common_enums::MandateStatus, @@ -103,6 +123,7 @@ pub enum WebhookResponseTracker { } impl WebhookResponseTracker { + #[cfg(feature = "v1")] pub fn get_payment_id(&self) -> Option { match self { Self::Payment { payment_id, .. } @@ -113,6 +134,18 @@ impl WebhookResponseTracker { Self::Payout { .. } => None, } } + + #[cfg(feature = "v2")] + pub fn get_payment_id(&self) -> Option { + match self { + Self::Payment { payment_id, .. } + | Self::Refund { payment_id, .. } + | Self::Dispute { payment_id, .. } => Some(payment_id.to_owned()), + Self::NoEffect | Self::Mandate { .. } => None, + #[cfg(feature = "payouts")] + Self::Payout { .. } => None, + } + } } impl From for WebhookFlow { @@ -227,18 +260,36 @@ pub struct OutgoingWebhook { #[derive(Debug, Clone, Serialize, ToSchema)] #[serde(tag = "type", content = "object", rename_all = "snake_case")] +#[cfg(feature = "v1")] +pub enum OutgoingWebhookContent { + #[schema(value_type = PaymentsResponse, title = "PaymentsResponse")] + PaymentDetails(Box), + #[schema(value_type = RefundResponse, title = "RefundResponse")] + RefundDetails(Box), + #[schema(value_type = DisputeResponse, title = "DisputeResponse")] + DisputeDetails(Box), + #[schema(value_type = MandateResponse, title = "MandateResponse")] + MandateDetails(Box), + #[cfg(feature = "payouts")] + #[schema(value_type = PayoutCreateResponse, title = "PayoutCreateResponse")] + PayoutDetails(Box), +} + +#[derive(Debug, Clone, Serialize, ToSchema)] +#[serde(tag = "type", content = "object", rename_all = "snake_case")] +#[cfg(feature = "v2")] pub enum OutgoingWebhookContent { #[schema(value_type = PaymentsResponse, title = "PaymentsResponse")] - PaymentDetails(payments::PaymentsResponse), + PaymentDetails(Box), #[schema(value_type = RefundResponse, title = "RefundResponse")] - RefundDetails(refunds::RefundResponse), + RefundDetails(Box), #[schema(value_type = DisputeResponse, title = "DisputeResponse")] DisputeDetails(Box), #[schema(value_type = MandateResponse, title = "MandateResponse")] MandateDetails(Box), #[cfg(feature = "payouts")] #[schema(value_type = PayoutCreateResponse, title = "PayoutCreateResponse")] - PayoutDetails(payouts::PayoutCreateResponse), + PayoutDetails(Box), } #[derive(Debug, Clone, Serialize)] diff --git a/crates/cards/Cargo.toml b/crates/cards/Cargo.toml index 1178568d72e2..1d194e8690a7 100644 --- a/crates/cards/Cargo.toml +++ b/crates/cards/Cargo.toml @@ -11,9 +11,11 @@ license.workspace = true [dependencies] error-stack = "0.4.1" +once_cell = "1.19.0" serde = { version = "1.0.197", features = ["derive"] } thiserror = "1.0.58" time = "0.3.35" +regex = "1.10.4" # First party crates common_utils = { version = "0.1.0", path = "../common_utils" } diff --git a/crates/cards/src/validate.rs b/crates/cards/src/validate.rs index 1af9bf582b0f..725d05b4807f 100644 --- a/crates/cards/src/validate.rs +++ b/crates/cards/src/validate.rs @@ -1,19 +1,19 @@ -use std::{fmt, ops::Deref, str::FromStr}; +use std::{collections::HashMap, fmt, ops::Deref, str::FromStr}; +use common_utils::errors::ValidationError; +use error_stack::report; use masking::{PeekInterface, Strategy, StrongSecret, WithType}; +use once_cell::sync::Lazy; +use regex::Regex; #[cfg(not(target_arch = "wasm32"))] use router_env::{logger, which as router_env_which, Env}; use serde::{Deserialize, Deserializer, Serialize}; use thiserror::Error; -/// /// Minimum limit of a card number will not be less than 8 by ISO standards -/// pub const MIN_CARD_NUMBER_LENGTH: usize = 8; -/// /// 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)] @@ -46,6 +46,60 @@ impl CardNumber { .rev() .collect::() } + pub fn is_cobadged_card(&self) -> Result> { + /// Regex to identify card networks + static CARD_NETWORK_REGEX: Lazy>> = Lazy::new( + || { + let mut map = HashMap::new(); + map.insert( + "Mastercard", + Regex::new(r"^(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[0-1][0-9]|2720|5[1-5])"), + ); + map.insert("American Express", Regex::new(r"^3[47]")); + map.insert("Visa", Regex::new(r"^4")); + map.insert( + "Discover", + Regex::new( + r"^(6011|64[4-9]|65|622126|622[1-9][0-9][0-9]|6229[0-1][0-9]|622925)", + ), + ); + map.insert( + "Maestro", + Regex::new(r"^(5018|5081|5044|504681|504993|5020|502260|5038|5893|603845|603123|6304|6759|676[1-3]|6220|504834|504817|504645|504775|600206|627741)"), + ); + map.insert( + "RuPay", + Regex::new(r"^(508227|508[5-9]|603741|60698[5-9]|60699|607[0-8]|6079[0-7]|60798[0-4]|60800[1-9]|6080[1-9]|608[1-4]|608500|6521[5-9]|652[2-9]|6530|6531[0-4]|817290|817368|817378|353800|82)"), + ); + map.insert("Diners Club", Regex::new(r"^(36|38|39|30[0-5])")); + map.insert("JCB", Regex::new(r"^35(2[89]|[3-8][0-9])")); + map.insert("CarteBlanche", Regex::new(r"^389[0-9]{11}$")); + map.insert("Sodex", Regex::new(r"^(637513)")); + map.insert("BAJAJ", Regex::new(r"^(203040)")); + map.insert("CartesBancaires", Regex::new(r"^(401(005|006|581)|4021(01|02)|403550|405936|406572|41(3849|4819|50(56|59|62|71|74)|6286|65(37|79)|71[7])|420110|423460|43(47(21|22)|50(48|49|50|51|52)|7875|95(09|11|15|39|98)|96(03|18|19|20|22|72))|4424(48|49|50|51|52|57)|448412|4505(19|60)|45(33|56[6-8]|61|62[^3]|6955|7452|7717|93[02379])|46(099|54(76|77)|6258|6575|98[023])|47(4107|71(73|74|86)|72(65|93)|9619)|48(1091|3622|6519)|49(7|83[5-9]|90(0[1-6]|1[0-6]|2[0-3]|3[0-3]|4[0-3]|5[0-2]|68|9[256789]))|5075(89|90|93|94|97)|51(0726|3([0-7]|8[56]|9(00|38))|5214|62(07|36)|72(22|43)|73(65|66)|7502|7647|8101|9920)|52(0993|1662|3718|7429|9227|93(13|14|31)|94(14|21|30|40|47|55|56|[6-9])|9542)|53(0901|10(28|30)|1195|23(4[4-7])|2459|25(09|34|54|56)|3801|41(02|05|11)|50(29|66)|5324|61(07|15)|71(06|12)|8011)|54(2848|5157|9538|98(5[89]))|55(39(79|93)|42(05|60)|4965|7008|88(67|82)|89(29|4[23])|9618|98(09|10))|56(0408|12(0[2-6]|4[134]|5[04678]))|58(17(0[0-7]|15|2[14]|3[16789]|4[0-9]|5[016]|6[269]|7[3789]|8[0-7]|9[017])|55(0[2-5]|7[7-9]|8[0-2])))")); + map + }, + ); + let mut no_of_supported_card_networks = 0; + + let card_number_str = self.get_card_no(); + for (_, regex) in CARD_NETWORK_REGEX.iter() { + let card_regex = match regex.as_ref() { + Ok(regex) => Ok(regex), + Err(_) => Err(report!(ValidationError::InvalidValue { + message: "Invalid regex expression".into(), + })), + }?; + + if card_regex.is_match(&card_number_str) { + no_of_supported_card_networks += 1; + if no_of_supported_card_networks > 1 { + break; + } + } + } + Ok(no_of_supported_card_networks > 1) + } } impl FromStr for CardNumber { @@ -86,11 +140,9 @@ pub fn sanitize_card_number(card_number: &str) -> Result Result, CardNumberValidationErr> { let data = number.chars().try_fold( Vec::with_capacity(MAX_CARD_NUMBER_LENGTH), diff --git a/crates/common_enums/Cargo.toml b/crates/common_enums/Cargo.toml index da03b530eb8c..92fc2f02066b 100644 --- a/crates/common_enums/Cargo.toml +++ b/crates/common_enums/Cargo.toml @@ -22,6 +22,7 @@ utoipa = { version = "4.2.0", features = ["preserve_order", "preserve_path_order # First party crates router_derive = { version = "0.1.0", path = "../router_derive" } +masking = { version = "0.1.0", path = "../masking" } [dev-dependencies] serde_json = "1.0.115" diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs new file mode 100644 index 000000000000..127d5a9901c6 --- /dev/null +++ b/crates/common_enums/src/connector_enums.rs @@ -0,0 +1,134 @@ +use utoipa::ToSchema; +#[derive( + Clone, + Copy, + Debug, + Eq, + Hash, + PartialEq, + serde::Serialize, + serde::Deserialize, + strum::Display, + strum::EnumString, + strum::EnumIter, + strum::VariantNames, + ToSchema, +)] +#[router_derive::diesel_enum(storage_type = "db_enum")] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +/// Connectors eligible for payments routing +pub enum RoutableConnectors { + Adyenplatform, + #[cfg(feature = "dummy_connector")] + #[serde(rename = "phonypay")] + #[strum(serialize = "phonypay")] + DummyConnector1, + #[cfg(feature = "dummy_connector")] + #[serde(rename = "fauxpay")] + #[strum(serialize = "fauxpay")] + DummyConnector2, + #[cfg(feature = "dummy_connector")] + #[serde(rename = "pretendpay")] + #[strum(serialize = "pretendpay")] + DummyConnector3, + #[cfg(feature = "dummy_connector")] + #[serde(rename = "stripe_test")] + #[strum(serialize = "stripe_test")] + DummyConnector4, + #[cfg(feature = "dummy_connector")] + #[serde(rename = "adyen_test")] + #[strum(serialize = "adyen_test")] + DummyConnector5, + #[cfg(feature = "dummy_connector")] + #[serde(rename = "checkout_test")] + #[strum(serialize = "checkout_test")] + DummyConnector6, + #[cfg(feature = "dummy_connector")] + #[serde(rename = "paypal_test")] + #[strum(serialize = "paypal_test")] + DummyConnector7, + Aci, + Adyen, + Airwallex, + // Amazonpay, + Authorizedotnet, + Bankofamerica, + Billwerk, + Bitpay, + Bambora, + Bamboraapac, + Bluesnap, + Boku, + Braintree, + Cashtocode, + Checkout, + Coinbase, + Cryptopay, + Cybersource, + Datatrans, + Deutschebank, + Digitalvirgo, + Dlocal, + Ebanx, + Elavon, + Fiserv, + Fiservemea, + Fiuu, + Forte, + Globalpay, + Globepay, + Gocardless, + Helcim, + Iatapay, + // Inespay, + Itaubank, + Jpmorgan, + Klarna, + Mifinity, + Mollie, + Multisafepay, + Nexinets, + Nexixpay, + Nmi, + // Nomupay, + Noon, + Novalnet, + Nuvei, + // Opayo, added as template code for future usage + Opennode, + // Payeezy, As psync and rsync are not supported by this connector, it is added as template code for future usage + Paybox, + Payme, + Payone, + Paypal, + Payu, + Placetopay, + Powertranz, + Prophetpay, + Rapyd, + Razorpay, + // Redsys, + Riskified, + Shift4, + Signifyd, + Square, + Stax, + Stripe, + // Taxjar, + Trustpay, + // Thunes + // Tsys, + Tsys, + // UnifiedAuthenticationService, + Volt, + Wellsfargo, + // Wellsfargopayout, + Wise, + Worldline, + Worldpay, + Xendit, + Zen, + Plaid, + Zsl, +} diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 067ef1000a1d..0833ab37e3f8 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -1,8 +1,14 @@ +mod payments; +mod ui; use std::num::{ParseFloatError, TryFromIntError}; +pub use payments::ProductType; use serde::{Deserialize, Serialize}; +pub use ui::*; use utoipa::ToSchema; +pub use super::connector_enums::RoutableConnectors; + #[doc(hidden)] pub mod diesel_exports { pub use super::{ @@ -16,6 +22,8 @@ pub mod diesel_exports { DbMandateStatus as MandateStatus, DbPaymentMethodIssuerCode as PaymentMethodIssuerCode, DbPaymentType as PaymentType, DbRefundStatus as RefundStatus, DbRequestIncrementalAuthorization as RequestIncrementalAuthorization, + DbScaExemptionType as ScaExemptionType, + DbSuccessBasedRoutingConclusiveState as SuccessBasedRoutingConclusiveState, DbWebhookDeliveryAttempt as WebhookDeliveryAttempt, }; } @@ -140,131 +148,6 @@ pub enum AttemptStatus { DeviceDataCollectionPending, } -#[derive( - Clone, - Copy, - Debug, - Eq, - Hash, - PartialEq, - serde::Serialize, - serde::Deserialize, - strum::Display, - strum::EnumString, - strum::EnumIter, - strum::VariantNames, - ToSchema, -)] -#[router_derive::diesel_enum(storage_type = "db_enum")] -#[serde(rename_all = "snake_case")] -#[strum(serialize_all = "snake_case")] -/// Connectors eligible for payments routing -pub enum RoutableConnectors { - // Nexixpay, - Adyenplatform, - #[cfg(feature = "dummy_connector")] - #[serde(rename = "phonypay")] - #[strum(serialize = "phonypay")] - DummyConnector1, - #[cfg(feature = "dummy_connector")] - #[serde(rename = "fauxpay")] - #[strum(serialize = "fauxpay")] - DummyConnector2, - #[cfg(feature = "dummy_connector")] - #[serde(rename = "pretendpay")] - #[strum(serialize = "pretendpay")] - DummyConnector3, - #[cfg(feature = "dummy_connector")] - #[serde(rename = "stripe_test")] - #[strum(serialize = "stripe_test")] - DummyConnector4, - #[cfg(feature = "dummy_connector")] - #[serde(rename = "adyen_test")] - #[strum(serialize = "adyen_test")] - DummyConnector5, - #[cfg(feature = "dummy_connector")] - #[serde(rename = "checkout_test")] - #[strum(serialize = "checkout_test")] - DummyConnector6, - #[cfg(feature = "dummy_connector")] - #[serde(rename = "paypal_test")] - #[strum(serialize = "paypal_test")] - DummyConnector7, - Aci, - Adyen, - Airwallex, - Authorizedotnet, - Bankofamerica, - Billwerk, - Bitpay, - Bambora, - Bamboraapac, - Bluesnap, - Boku, - Braintree, - Cashtocode, - Checkout, - Coinbase, - Cryptopay, - Cybersource, - Datatrans, - Deutschebank, - Dlocal, - Ebanx, - Fiserv, - Fiservemea, - Fiuu, - Forte, - Globalpay, - Globepay, - Gocardless, - Helcim, - Iatapay, - Itaubank, - Klarna, - Mifinity, - Mollie, - Multisafepay, - Nexinets, - Nmi, - Noon, - Novalnet, - Nuvei, - // Opayo, added as template code for future usage - Opennode, - // Payeezy, As psync and rsync are not supported by this connector, it is added as template code for future usage - Paybox, - Payme, - Payone, - Paypal, - Payu, - Placetopay, - Powertranz, - Prophetpay, - Rapyd, - Razorpay, - Riskified, - Shift4, - Signifyd, - Square, - Stax, - Stripe, - // Taxjar, - Trustpay, - // Thunes - // Tsys, - Tsys, - Volt, - Wellsfargo, - // Wellsfargopayout, - Wise, - Worldline, - Worldpay, - Zen, - Plaid, - Zsl, -} - impl AttemptStatus { pub fn is_terminal_status(self) -> bool { match self { @@ -403,25 +286,25 @@ pub enum AuthorizationStatus { Unresolved, } -// #[derive( -// Clone, -// Debug, -// Eq, -// PartialEq, -// serde::Deserialize, -// serde::Serialize, -// strum::Display, -// strum::EnumString, -// ToSchema, -// Hash, -// )] -// #[router_derive::diesel_enum(storage_type = "text")] -// #[serde(rename_all = "snake_case")] -// #[strum(serialize_all = "snake_case")] -// pub enum SessionUpdateStatus { -// Success, -// Failure, -// } +#[derive( + Clone, + Debug, + Eq, + PartialEq, + serde::Deserialize, + serde::Serialize, + strum::Display, + strum::EnumString, + ToSchema, + Hash, +)] +#[router_derive::diesel_enum(storage_type = "text")] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum SessionUpdateStatus { + Success, + Failure, +} #[derive( Clone, @@ -474,6 +357,8 @@ pub enum CaptureMethod { ManualMultiple, /// The capture can be scheduled to automatically get triggered at a specific date & time Scheduled, + /// Handles separate auth and capture sequentially; same as `Automatic` for most connectors. + SequentialAutomatic, } /// Type of the Connector for the financial use case. Could range from Payments to Accounting to Banking. @@ -557,6 +442,7 @@ pub enum CallConnectorAction { #[router_derive::diesel_enum(storage_type = "db_enum")] pub enum Currency { AED, + AFN, ALL, AMD, ANG, @@ -576,10 +462,12 @@ pub enum Currency { BOB, BRL, BSD, + BTN, BWP, BYN, BZD, CAD, + CDF, CHF, CLP, CNY, @@ -593,6 +481,7 @@ pub enum Currency { DOP, DZD, EGP, + ERN, ETB, EUR, FJD, @@ -614,6 +503,8 @@ pub enum Currency { ILS, INR, IQD, + IRR, + ISK, JMD, JOD, JPY, @@ -621,6 +512,7 @@ pub enum Currency { KGS, KHR, KMF, + KPW, KRW, KWD, KYD, @@ -667,6 +559,7 @@ pub enum Currency { SAR, SBD, SCR, + SDG, SEK, SGD, SHP, @@ -677,8 +570,11 @@ pub enum Currency { SSP, STN, SVC, + SYP, SZL, THB, + TJS, + TMT, TND, TOP, TRY, @@ -702,17 +598,18 @@ pub enum Currency { YER, ZAR, ZMW, + ZWL, } impl Currency { /// Convert the amount to its base denomination based on Currency and return String - pub fn to_currency_base_unit(&self, amount: i64) -> Result { + pub fn to_currency_base_unit(self, amount: i64) -> Result { let amount_f64 = self.to_currency_base_unit_asf64(amount)?; Ok(format!("{amount_f64:.2}")) } /// Convert the amount to its base denomination based on Currency and return f64 - pub fn to_currency_base_unit_asf64(&self, amount: i64) -> Result { + pub fn to_currency_base_unit_asf64(self, amount: i64) -> Result { let amount_f64: f64 = u32::try_from(amount)?.into(); let amount = if self.is_zero_decimal_currency() { amount_f64 @@ -725,7 +622,7 @@ impl Currency { } ///Convert the higher decimal amount to its base absolute units - pub fn to_currency_lower_unit(&self, amount: String) -> Result { + pub fn to_currency_lower_unit(self, amount: String) -> Result { let amount_f64 = amount.parse::()?; let amount_string = if self.is_zero_decimal_currency() { amount_f64 @@ -741,7 +638,7 @@ impl Currency { /// Paypal Connector accepts Zero and Two decimal currency but not three decimal and it should be updated as required for 3 decimal currencies. /// Paypal Ref - https://developer.paypal.com/docs/reports/reference/paypal-supported-currencies/ pub fn to_currency_base_unit_with_zero_decimal_check( - &self, + self, amount: i64, ) -> Result { let amount_f64 = self.to_currency_base_unit_asf64(amount)?; @@ -752,9 +649,10 @@ impl Currency { } } - pub fn iso_4217(&self) -> &'static str { - match *self { + pub fn iso_4217(self) -> &'static str { + match self { Self::AED => "784", + Self::AFN => "971", Self::ALL => "008", Self::AMD => "051", Self::ANG => "532", @@ -774,10 +672,12 @@ impl Currency { Self::BOB => "068", Self::BRL => "986", Self::BSD => "044", + Self::BTN => "064", Self::BWP => "072", Self::BYN => "933", Self::BZD => "084", Self::CAD => "124", + Self::CDF => "976", Self::CHF => "756", Self::CLP => "152", Self::COP => "170", @@ -790,6 +690,7 @@ impl Currency { Self::DOP => "214", Self::DZD => "012", Self::EGP => "818", + Self::ERN => "232", Self::ETB => "230", Self::EUR => "978", Self::FJD => "242", @@ -811,6 +712,8 @@ impl Currency { Self::ILS => "376", Self::INR => "356", Self::IQD => "368", + Self::IRR => "364", + Self::ISK => "352", Self::JMD => "388", Self::JOD => "400", Self::JPY => "392", @@ -818,6 +721,7 @@ impl Currency { Self::KGS => "417", Self::KHR => "116", Self::KMF => "174", + Self::KPW => "408", Self::KRW => "410", Self::KWD => "414", Self::KYD => "136", @@ -865,6 +769,7 @@ impl Currency { Self::SAR => "682", Self::SBD => "090", Self::SCR => "690", + Self::SDG => "938", Self::SEK => "752", Self::SGD => "702", Self::SHP => "654", @@ -875,8 +780,11 @@ impl Currency { Self::SSP => "728", Self::STN => "930", Self::SVC => "222", + Self::SYP => "760", Self::SZL => "748", Self::THB => "764", + Self::TJS => "972", + Self::TMT => "934", Self::TND => "788", Self::TOP => "776", Self::TRY => "949", @@ -899,6 +807,7 @@ impl Currency { Self::YER => "886", Self::ZAR => "710", Self::ZMW => "967", + Self::ZWL => "932", } } @@ -908,6 +817,7 @@ impl Currency { | Self::CLP | Self::DJF | Self::GNF + | Self::IRR | Self::JPY | Self::KMF | Self::KRW @@ -921,6 +831,7 @@ impl Currency { | Self::XOF | Self::XPF => true, Self::AED + | Self::AFN | Self::ALL | Self::AMD | Self::ANG @@ -939,10 +850,12 @@ impl Currency { | Self::BOB | Self::BRL | Self::BSD + | Self::BTN | Self::BWP | Self::BYN | Self::BZD | Self::CAD + | Self::CDF | Self::CHF | Self::CNY | Self::COP @@ -954,6 +867,7 @@ impl Currency { | Self::DOP | Self::DZD | Self::EGP + | Self::ERN | Self::ETB | Self::EUR | Self::FJD @@ -974,11 +888,13 @@ impl Currency { | Self::ILS | Self::INR | Self::IQD + | Self::ISK | Self::JMD | Self::JOD | Self::KES | Self::KGS | Self::KHR + | Self::KPW | Self::KWD | Self::KYD | Self::KZT @@ -1021,6 +937,7 @@ impl Currency { | Self::SAR | Self::SBD | Self::SCR + | Self::SDG | Self::SEK | Self::SGD | Self::SHP @@ -1031,8 +948,11 @@ impl Currency { | Self::SSP | Self::STN | Self::SVC + | Self::SYP | Self::SZL | Self::THB + | Self::TJS + | Self::TMT | Self::TND | Self::TOP | Self::TRY @@ -1048,7 +968,8 @@ impl Currency { | Self::XCD | Self::YER | Self::ZAR - | Self::ZMW => false, + | Self::ZMW + | Self::ZWL => false, } } @@ -1058,6 +979,7 @@ impl Currency { true } Self::AED + | Self::AFN | Self::ALL | Self::AMD | Self::AOA @@ -1076,10 +998,12 @@ impl Currency { | Self::BOB | Self::BRL | Self::BSD + | Self::BTN | Self::BWP | Self::BYN | Self::BZD | Self::CAD + | Self::CDF | Self::CHF | Self::CLP | Self::CNY @@ -1093,6 +1017,7 @@ impl Currency { | Self::DOP | Self::DZD | Self::EGP + | Self::ERN | Self::ETB | Self::EUR | Self::FJD @@ -1113,12 +1038,15 @@ impl Currency { | Self::IDR | Self::ILS | Self::INR + | Self::IRR + | Self::ISK | Self::JMD | Self::JPY | Self::KES | Self::KGS | Self::KHR | Self::KMF + | Self::KPW | Self::KRW | Self::KYD | Self::KZT @@ -1162,6 +1090,7 @@ impl Currency { | Self::SAR | Self::SBD | Self::SCR + | Self::SDG | Self::SEK | Self::SGD | Self::SHP @@ -1172,8 +1101,11 @@ impl Currency { | Self::SSP | Self::STN | Self::SVC + | Self::SYP | Self::SZL | Self::THB + | Self::TJS + | Self::TMT | Self::TOP | Self::TRY | Self::TTD @@ -1194,7 +1126,8 @@ impl Currency { | Self::XOF | Self::YER | Self::ZAR - | Self::ZMW => false, + | Self::ZMW + | Self::ZWL => false, } } @@ -1342,20 +1275,56 @@ pub enum MerchantStorageScheme { #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] pub enum IntentStatus { + /// The payment has succeeded. Refunds and disputes can be initiated. + /// Manual retries are not allowed to be performed. Succeeded, + /// The payment has failed. Refunds and disputes cannot be initiated. + /// This payment can be retried manually with a new payment attempt. Failed, + /// This payment has been cancelled. Cancelled, + /// This payment is still being processed by the payment processor. + /// The status update might happen through webhooks or polling with the connector. Processing, + /// The payment is waiting on some action from the customer. RequiresCustomerAction, + /// The payment is waiting on some action from the merchant + /// This would be in case of manual fraud approval RequiresMerchantAction, + /// The payment is waiting to be confirmed with the payment method by the customer. RequiresPaymentMethod, #[default] RequiresConfirmation, + /// The payment has been authorized, and it waiting to be captured. RequiresCapture, + /// The payment has been captured partially. The remaining amount is cannot be captured. PartiallyCaptured, + /// The payment has been captured partially and the remaining amount is capturable PartiallyCapturedAndCapturable, } +impl IntentStatus { + /// Indicates whether the syncing with the connector should be allowed or not + pub fn should_force_sync_with_connector(self) -> bool { + match self { + // Confirm has not happened yet + Self::RequiresConfirmation + | Self::RequiresPaymentMethod + // Once the status is success, failed or cancelled need not force sync with the connector + | Self::Succeeded + | Self::Failed + | Self::Cancelled + | Self::PartiallyCaptured + | Self::RequiresCapture => false, + Self::Processing + | Self::RequiresCustomerAction + | Self::RequiresMerchantAction + | Self::PartiallyCapturedAndCapturable + => true, + } + } +} + /// Indicates that you intend to make future payments with the payment methods used for this Payment. Providing this parameter will attach the payment method to the Customer, if present, after the Payment is confirmed and any required actions from the user are complete. /// - On_session - Payment method saved only at hyperswitch when consent is provided by the user. CVV will asked during the returning user payment /// - Off_session - Payment method saved at both hyperswitch and Processor when consent is provided by the user. No input is required during the returning user payment. @@ -1506,6 +1475,18 @@ pub enum PaymentExperience { InvokePaymentApp, /// Contains the data for displaying wait screen DisplayWaitScreen, + /// Represents that otp needs to be collect and contains if consent is required + CollectOtp, +} + +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, strum::Display)] +#[serde(rename_all = "lowercase")] +pub enum SamsungPayCardBrand { + Visa, + MasterCard, + Amex, + Discover, + Unknown, } /// Indicates the sub type of payment method. Eg: 'google_pay' & 'apple_pay' for wallets. @@ -1592,6 +1573,7 @@ pub enum PaymentMethodType { OpenBankingUk, PayBright, Paypal, + Paze, Pix, PaySafeCard, Przelewy24, @@ -1623,8 +1605,11 @@ pub enum PaymentMethodType { Mifinity, #[serde(rename = "open_banking_pis")] OpenBankingPIS, + DirectCarrierBilling, } +impl masking::SerializableSecret for PaymentMethodType {} + /// Indicates the type of payment method. Eg: 'card', 'wallet', etc. #[derive( Clone, @@ -1661,6 +1646,7 @@ pub enum PaymentMethod { Voucher, GiftCard, OpenBanking, + MobilePayment, } /// The type of the payment that differentiates between normal and various types of mandate payments. Use 'setup_mandate' in case of zero auth flow. @@ -1688,6 +1674,29 @@ pub enum PaymentType { RecurringMandate, } +/// SCA Exemptions types available for authentication +#[derive( + Clone, + Copy, + Debug, + Default, + Eq, + PartialEq, + serde::Deserialize, + serde::Serialize, + strum::Display, + strum::EnumString, + ToSchema, +)] +#[router_derive::diesel_enum(storage_type = "db_enum")] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum ScaExemptionType { + #[default] + LowValue, + TransactionRiskAnalysis, +} + #[derive( Clone, Copy, @@ -1719,6 +1728,53 @@ pub enum RefundStatus { TransactionFailure, } +#[derive( + Clone, + Copy, + Debug, + Default, + Eq, + Hash, + PartialEq, + strum::Display, + strum::EnumString, + strum::EnumIter, + serde::Serialize, + serde::Deserialize, + ToSchema, +)] +#[router_derive::diesel_enum(storage_type = "db_enum")] +#[strum(serialize_all = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum RelayStatus { + Created, + #[default] + Pending, + Success, + Failure, +} + +#[derive( + Clone, + Copy, + Debug, + Eq, + Hash, + PartialEq, + strum::Display, + strum::EnumString, + strum::EnumIter, + serde::Serialize, + serde::Deserialize, + ToSchema, +)] +#[router_derive::diesel_enum(storage_type = "db_enum")] +#[strum(serialize_all = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum RelayType { + Refund, +} + #[derive( Clone, Copy, @@ -2490,7 +2546,7 @@ pub enum ClientPlatform { } impl PaymentSource { - pub fn is_for_internal_use_only(&self) -> bool { + pub fn is_for_internal_use_only(self) -> bool { match self { Self::Dashboard | Self::Sdk | Self::MerchantServer | Self::Postman => false, Self::Webhook | Self::ExternalAuthenticator => true, @@ -2582,12 +2638,17 @@ pub enum AuthenticationConnectors { Threedsecureio, Netcetera, Gpayments, + CtpMastercard, + UnifiedAuthenticationService, } impl AuthenticationConnectors { - pub fn is_separate_version_call_required(&self) -> bool { + pub fn is_separate_version_call_required(self) -> bool { match self { - Self::Threedsecureio | Self::Netcetera => false, + Self::Threedsecureio + | Self::Netcetera + | Self::CtpMastercard + | Self::UnifiedAuthenticationService => false, Self::Gpayments => true, } } @@ -2619,15 +2680,15 @@ pub enum AuthenticationStatus { } impl AuthenticationStatus { - pub fn is_terminal_status(&self) -> bool { + pub fn is_terminal_status(self) -> bool { match self { Self::Started | Self::Pending => false, Self::Success | Self::Failed => true, } } - pub fn is_failed(&self) -> bool { - self == &Self::Failed + pub fn is_failed(self) -> bool { + self == Self::Failed } } @@ -2810,10 +2871,67 @@ pub enum PermissionGroup { AnalyticsView, UsersView, UsersManage, + // TODO: To be deprecated, make sure DB is migrated before removing MerchantDetailsView, + // TODO: To be deprecated, make sure DB is migrated before removing MerchantDetailsManage, + // TODO: To be deprecated, make sure DB is migrated before removing OrganizationManage, + AccountView, + AccountManage, + ReconReportsView, + ReconReportsManage, + ReconOpsView, + ReconOpsManage, + // TODO: To be deprecated, make sure DB is migrated before removing + ReconOps, +} + +#[derive(Clone, Debug, serde::Serialize, PartialEq, Eq, Hash, strum::EnumIter)] +pub enum ParentGroup { + Operations, + Connectors, + Workflows, + Analytics, + Users, ReconOps, + ReconReports, + Account, +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub enum Resource { + Payment, + Refund, + ApiKey, + Account, + Connector, + Routing, + Dispute, + Mandate, + Customer, + Analytics, + ThreeDsDecisionManager, + SurchargeDecisionManager, + User, + WebhookEvent, + Payout, + Report, + ReconToken, + ReconFiles, + ReconAndSettlementAnalytics, + ReconUpload, + ReconReports, + RunRecon, + ReconConfig, +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, serde::Serialize, Hash)] +#[serde(rename_all = "snake_case")] +pub enum PermissionScope { + Read = 0, + Write = 1, } /// Name of banks supported by Hyperswitch @@ -3144,6 +3262,7 @@ pub enum ApiVersion { #[strum(serialize_all = "snake_case")] #[serde(rename_all = "snake_case")] pub enum EntityType { + Tenant = 3, Organization = 2, Merchant = 1, Profile = 0, @@ -3219,10 +3338,20 @@ pub enum DeleteStatus { } #[derive( - Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, strum::Display, Hash, + Clone, + Copy, + Debug, + Eq, + PartialEq, + serde::Deserialize, + serde::Serialize, + strum::Display, + Hash, + strum::EnumString, )] #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] +#[router_derive::diesel_enum(storage_type = "db_enum")] pub enum SuccessBasedRoutingConclusiveState { // pc: payment connector // sc: success based routing outcome/first connector @@ -3269,8 +3398,9 @@ pub enum MitExemptionRequest { Skip, } -/// Set to true to indicate that the customer is in your checkout flow during this payment, and therefore is able to authenticate. This parameter should be false when merchant's doing merchant initiated payments and customer is not present while doing the payment. +/// Set to `present` to indicate that the customer is in your checkout flow during this payment, and therefore is able to authenticate. This parameter should be `absent` when merchant's doing merchant initiated payments and customer is not present while doing the payment. #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Default, ToSchema)] +#[serde(rename_all = "snake_case")] pub enum PresenceOfCustomerDuringPayment { /// Customer is present during the payment. This is the default value #[default] @@ -3279,7 +3409,40 @@ pub enum PresenceOfCustomerDuringPayment { Absent, } -#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Default, ToSchema)] +impl From for TransactionType { + fn from(connector_type: ConnectorType) -> Self { + match connector_type { + #[cfg(feature = "payouts")] + ConnectorType::PayoutProcessor => Self::Payout, + _ => Self::Payment, + } + } +} + +impl From for RelayStatus { + fn from(refund_status: RefundStatus) -> Self { + match refund_status { + RefundStatus::Failure | RefundStatus::TransactionFailure => Self::Failure, + RefundStatus::ManualReview | RefundStatus::Pending => Self::Pending, + RefundStatus::Success => Self::Success, + } + } +} + +impl From for RefundStatus { + fn from(relay_status: RelayStatus) -> Self { + match relay_status { + RelayStatus::Failure => Self::Failure, + RelayStatus::Pending | RelayStatus::Created => Self::Pending, + RelayStatus::Success => Self::Success, + } + } +} + +#[derive( + Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize, Default, ToSchema, +)] +#[serde(rename_all = "snake_case")] pub enum TaxCalculationOverride { /// Skip calling the external tax provider #[default] @@ -3288,7 +3451,28 @@ pub enum TaxCalculationOverride { Calculate, } -#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, Default, ToSchema)] +impl From> for TaxCalculationOverride { + fn from(value: Option) -> Self { + match value { + Some(true) => Self::Calculate, + _ => Self::Skip, + } + } +} + +impl TaxCalculationOverride { + pub fn as_bool(self) -> bool { + match self { + Self::Skip => false, + Self::Calculate => true, + } + } +} + +#[derive( + Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize, Default, ToSchema, +)] +#[serde(rename_all = "snake_case")] pub enum SurchargeCalculationOverride { /// Skip calculating surcharge #[default] @@ -3296,3 +3480,136 @@ pub enum SurchargeCalculationOverride { /// Calculate surcharge Calculate, } + +impl From> for SurchargeCalculationOverride { + fn from(value: Option) -> Self { + match value { + Some(true) => Self::Calculate, + _ => Self::Skip, + } + } +} + +impl SurchargeCalculationOverride { + pub fn as_bool(self) -> bool { + match self { + Self::Skip => false, + Self::Calculate => true, + } + } +} + +/// Connector Mandate Status +#[derive( + Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, strum::Display, +)] +#[strum(serialize_all = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum ConnectorMandateStatus { + /// Indicates that the connector mandate is active and can be used for payments. + Active, + /// Indicates that the connector mandate is not active and hence cannot be used for payments. + Inactive, +} + +#[derive( + Clone, + Copy, + Debug, + strum::Display, + PartialEq, + Eq, + serde::Serialize, + serde::Deserialize, + strum::EnumString, + ToSchema, + PartialOrd, + Ord, +)] +#[router_derive::diesel_enum(storage_type = "text")] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum ErrorCategory { + FrmDecline, + ProcessorDowntime, + ProcessorDeclineUnauthorized, + IssueWithPaymentMethod, + ProcessorDeclineIncorrectData, +} + +#[derive( + Clone, + Debug, + Eq, + PartialEq, + serde::Deserialize, + serde::Serialize, + strum::Display, + strum::EnumString, + ToSchema, + Hash, +)] +pub enum PaymentChargeType { + #[serde(untagged)] + Stripe(StripeChargeType), +} + +#[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, +} + +/// Connector Access Method +#[derive( + Clone, + Copy, + Debug, + Eq, + PartialEq, + serde::Deserialize, + serde::Serialize, + strum::Display, + ToSchema, +)] +#[strum(serialize_all = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum PaymentConnectorCategory { + PaymentGateway, + AlternativePaymentMethod, + BankAcquirer, +} + +/// The status of the feature +#[derive( + Clone, + Copy, + Debug, + Eq, + PartialEq, + serde::Deserialize, + serde::Serialize, + strum::Display, + ToSchema, +)] +#[strum(serialize_all = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum FeatureStatus { + NotSupported, + Supported, +} diff --git a/crates/common_enums/src/enums/payments.rs b/crates/common_enums/src/enums/payments.rs new file mode 100644 index 000000000000..895303bab4f5 --- /dev/null +++ b/crates/common_enums/src/enums/payments.rs @@ -0,0 +1,14 @@ +use serde; +use utoipa::ToSchema; + +#[derive(Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize, Clone, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum ProductType { + #[default] + Physical, + Digital, + Travel, + Ride, + Event, + Accommodation, +} diff --git a/crates/common_enums/src/enums/ui.rs b/crates/common_enums/src/enums/ui.rs new file mode 100644 index 000000000000..aec6c7ed64a2 --- /dev/null +++ b/crates/common_enums/src/enums/ui.rs @@ -0,0 +1,149 @@ +use std::fmt; + +use serde::{de::Visitor, Deserialize, Deserializer, Serialize}; +use utoipa::ToSchema; + +#[derive( + Clone, + Copy, + Debug, + Default, + Eq, + Hash, + PartialEq, + Serialize, + Deserialize, + strum::Display, + strum::EnumString, + ToSchema, +)] +#[router_derive::diesel_enum(storage_type = "db_enum")] +#[serde(rename_all = "lowercase")] +pub enum ElementPosition { + Left, + #[default] + #[serde(rename = "top left")] + TopLeft, + Top, + #[serde(rename = "top right")] + TopRight, + Right, + #[serde(rename = "bottom right")] + BottomRight, + Bottom, + #[serde(rename = "bottom left")] + BottomLeft, + Center, +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, strum::Display, strum::EnumString, ToSchema)] +#[router_derive::diesel_enum(storage_type = "db_enum")] +pub enum ElementSize { + Variants(SizeVariants), + Percentage(u32), + Pixels(u32), +} + +#[derive( + Clone, + Copy, + Debug, + Default, + Eq, + Hash, + PartialEq, + Serialize, + Deserialize, + strum::Display, + strum::EnumString, + ToSchema, +)] +#[router_derive::diesel_enum(storage_type = "db_enum")] +#[serde(rename_all = "lowercase")] +#[strum(serialize_all = "lowercase")] +pub enum SizeVariants { + #[default] + Cover, + Contain, +} + +#[derive( + Clone, + Copy, + Debug, + Default, + Eq, + Hash, + PartialEq, + Serialize, + Deserialize, + strum::Display, + strum::EnumString, + ToSchema, +)] +#[router_derive::diesel_enum(storage_type = "db_enum")] +#[serde(rename_all = "lowercase")] +#[strum(serialize_all = "lowercase")] +pub enum PaymentLinkDetailsLayout { + #[default] + Layout1, + Layout2, +} + +impl<'de> Deserialize<'de> for ElementSize { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct ElementSizeVisitor; + + impl Visitor<'_> for ElementSizeVisitor { + type Value = ElementSize; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a string with possible values - contain, cover or values in percentage or pixels. For eg: 48px or 50%") + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + if let Some(percent) = value.strip_suffix('%') { + percent + .parse::() + .map(ElementSize::Percentage) + .map_err(E::custom) + } else if let Some(px) = value.strip_suffix("px") { + px.parse::() + .map(ElementSize::Pixels) + .map_err(E::custom) + } else { + match value { + "cover" => Ok(ElementSize::Variants(SizeVariants::Cover)), + "contain" => Ok(ElementSize::Variants(SizeVariants::Contain)), + _ => Err(E::custom("invalid size variant")), + } + } + } + } + + deserializer.deserialize_str(ElementSizeVisitor) + } +} + +impl Serialize for ElementSize { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + match self { + Self::Variants(variant) => serializer.serialize_str(variant.to_string().as_str()), + Self::Pixels(pixel_count) => { + serializer.serialize_str(format!("{}px", pixel_count).as_str()) + } + Self::Percentage(pixel_count) => { + serializer.serialize_str(format!("{}%", pixel_count).as_str()) + } + } + } +} diff --git a/crates/common_enums/src/lib.rs b/crates/common_enums/src/lib.rs index 14966d15b5f5..ec5b78d4a508 100644 --- a/crates/common_enums/src/lib.rs +++ b/crates/common_enums/src/lib.rs @@ -1,3 +1,4 @@ +pub mod connector_enums; pub mod enums; pub mod transformers; diff --git a/crates/common_enums/src/transformers.rs b/crates/common_enums/src/transformers.rs index db4162a8bb18..7611ae127ee7 100644 --- a/crates/common_enums/src/transformers.rs +++ b/crates/common_enums/src/transformers.rs @@ -2,7 +2,10 @@ use std::fmt::{Display, Formatter}; use serde::{Deserialize, Serialize}; -use crate::enums::{Country, CountryAlpha2, CountryAlpha3, PaymentMethod, PaymentMethodType}; +use crate::enums::{ + AttemptStatus, Country, CountryAlpha2, CountryAlpha3, IntentStatus, PaymentMethod, + PaymentMethodType, +}; impl Display for NumericCountryCodeParseError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { @@ -519,7 +522,7 @@ impl Country { CountryAlpha2::ZW => Self::Zimbabwe, } } - pub const fn to_alpha2(&self) -> CountryAlpha2 { + pub const fn to_alpha2(self) -> CountryAlpha2 { match self { Self::Afghanistan => CountryAlpha2::AF, Self::AlandIslands => CountryAlpha2::AX, @@ -1025,7 +1028,7 @@ impl Country { CountryAlpha3::ZWE => Self::Zimbabwe, } } - pub const fn to_alpha3(&self) -> CountryAlpha3 { + pub const fn to_alpha3(self) -> CountryAlpha3 { match self { Self::Afghanistan => CountryAlpha3::AFG, Self::AlandIslands => CountryAlpha3::ALA, @@ -1532,7 +1535,7 @@ impl Country { _ => Err(NumericCountryCodeParseError), } } - pub const fn to_numeric(&self) -> u32 { + pub const fn to_numeric(self) -> u32 { match self { Self::Afghanistan => 4, Self::AlandIslands => 248, @@ -1843,6 +1846,7 @@ impl From for PaymentMethod { PaymentMethodType::OnlineBankingThailand => Self::BankRedirect, PaymentMethodType::OnlineBankingPoland => Self::BankRedirect, PaymentMethodType::OnlineBankingSlovakia => Self::BankRedirect, + PaymentMethodType::Paze => Self::Wallet, PaymentMethodType::PermataBankTransfer => Self::BankTransfer, PaymentMethodType::Pix => Self::BankTransfer, PaymentMethodType::Pse => Self::BankTransfer, @@ -1883,6 +1887,7 @@ impl From for PaymentMethod { PaymentMethodType::Seicomart => Self::Voucher, PaymentMethodType::PayEasy => Self::Voucher, PaymentMethodType::OpenBankingPIS => Self::OpenBanking, + PaymentMethodType::DirectCarrierBilling => Self::MobilePayment, } } } @@ -1900,6 +1905,8 @@ mod custom_serde { use super::*; + // `serde::Serialize` implementation needs the function to accept `&Country` + #[allow(clippy::trivially_copy_pass_by_ref)] pub fn serialize(code: &Country, serializer: S) -> Result where S: serde::Serializer, @@ -1909,7 +1916,7 @@ mod custom_serde { struct FieldVisitor; - impl<'de> Visitor<'de> for FieldVisitor { + impl Visitor<'_> for FieldVisitor { type Value = CountryAlpha2; fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { @@ -1932,6 +1939,8 @@ mod custom_serde { use super::*; + // `serde::Serialize` implementation needs the function to accept `&Country` + #[allow(clippy::trivially_copy_pass_by_ref)] pub fn serialize(code: &Country, serializer: S) -> Result where S: serde::Serializer, @@ -1941,7 +1950,7 @@ mod custom_serde { struct FieldVisitor; - impl<'de> Visitor<'de> for FieldVisitor { + impl Visitor<'_> for FieldVisitor { type Value = CountryAlpha3; fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { @@ -1964,6 +1973,8 @@ mod custom_serde { use super::*; + // `serde::Serialize` implementation needs the function to accept `&Country` + #[allow(clippy::trivially_copy_pass_by_ref)] pub fn serialize(code: &Country, serializer: S) -> Result where S: serde::Serializer, @@ -1973,7 +1984,7 @@ mod custom_serde { struct FieldVisitor; - impl<'de> Visitor<'de> for FieldVisitor { + impl Visitor<'_> for FieldVisitor { type Value = u32; fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { @@ -2064,6 +2075,41 @@ impl super::PresenceOfCustomerDuringPayment { } } +impl From for IntentStatus { + fn from(s: AttemptStatus) -> Self { + match s { + AttemptStatus::Charged | AttemptStatus::AutoRefunded => Self::Succeeded, + + AttemptStatus::ConfirmationAwaited => Self::RequiresConfirmation, + AttemptStatus::PaymentMethodAwaited => Self::RequiresPaymentMethod, + + AttemptStatus::Authorized => Self::RequiresCapture, + AttemptStatus::AuthenticationPending | AttemptStatus::DeviceDataCollectionPending => { + Self::RequiresCustomerAction + } + AttemptStatus::Unresolved => Self::RequiresMerchantAction, + + AttemptStatus::PartialCharged => Self::PartiallyCaptured, + AttemptStatus::PartialChargedAndChargeable => Self::PartiallyCapturedAndCapturable, + AttemptStatus::Started + | AttemptStatus::AuthenticationSuccessful + | AttemptStatus::Authorizing + | AttemptStatus::CodInitiated + | AttemptStatus::VoidInitiated + | AttemptStatus::CaptureInitiated + | AttemptStatus::Pending => Self::Processing, + + AttemptStatus::AuthenticationFailed + | AttemptStatus::AuthorizationFailed + | AttemptStatus::VoidFailed + | AttemptStatus::RouterDeclined + | AttemptStatus::CaptureFailed + | AttemptStatus::Failure => Self::Failed, + AttemptStatus::Voided => Self::Cancelled, + } + } +} + #[cfg(test)] mod tests { #![allow(clippy::unwrap_used)] diff --git a/crates/common_types/Cargo.toml b/crates/common_types/Cargo.toml new file mode 100644 index 000000000000..33dd799f0f60 --- /dev/null +++ b/crates/common_types/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "common_types" +description = "Types shared across the request/response types and database types" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +license.workspace = true + +[features] +default = [] +v1 = ["common_utils/v1"] +v2 = ["common_utils/v2"] + +[dependencies] +diesel = "2.2.3" +serde = { version = "1.0.197", features = ["derive"] } +serde_json = "1.0.115" +utoipa = { version = "4.2.0", features = ["preserve_order", "preserve_path_order"] } + +common_enums = { version = "0.1.0", path = "../common_enums" } +common_utils = { version = "0.1.0", path = "../common_utils"} + +[lints] +workspace = true diff --git a/crates/common_types/src/lib.rs b/crates/common_types/src/lib.rs new file mode 100644 index 000000000000..b0b258ecb6d8 --- /dev/null +++ b/crates/common_types/src/lib.rs @@ -0,0 +1,7 @@ +//! Types shared across the request/response types and database types + +#![warn(missing_docs, missing_debug_implementations)] + +pub mod payment_methods; +pub mod payments; +pub mod refunds; diff --git a/crates/common_types/src/payment_methods.rs b/crates/common_types/src/payment_methods.rs new file mode 100644 index 000000000000..702d8c0e7000 --- /dev/null +++ b/crates/common_types/src/payment_methods.rs @@ -0,0 +1,126 @@ +//! Common types to be used in payment methods + +use diesel::{ + backend::Backend, deserialize, deserialize::FromSql, sql_types::Jsonb, AsExpression, Queryable, +}; +use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; + +/// Details of all the payment methods enabled for the connector for the given merchant account +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, AsExpression)] +#[serde(deny_unknown_fields)] +#[diesel(sql_type = Jsonb)] +pub struct PaymentMethodsEnabled { + /// Type of payment method. + #[schema(value_type = PaymentMethod,example = "card")] + pub payment_method_type: common_enums::PaymentMethod, + + /// Payment method configuration, this includes all the filters associated with the payment method + pub payment_method_subtypes: Option>, +} + +/// Details of a specific payment method subtype enabled for the connector for the given merchant account +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema, PartialEq, Eq, Hash)] +pub struct RequestPaymentMethodTypes { + /// The payment method subtype + #[schema(value_type = PaymentMethodType)] + pub payment_method_subtype: common_enums::PaymentMethodType, + + /// The payment experience for the payment method + #[schema(value_type = Option)] + pub payment_experience: Option, + + /// List of cards networks that are enabled for this payment method, applicable for credit and debit payment method subtypes only + #[schema(value_type = Option>)] + pub card_networks: Option>, + /// List of currencies accepted or has the processing capabilities of the processor + #[schema(example = json!( + { + "type": "enable_only", + "list": ["USD", "INR"] + } + ), value_type = Option)] + pub accepted_currencies: Option, + + /// List of Countries accepted or has the processing capabilities of the processor + #[schema(example = json!( + { + "type": "enable_only", + "list": ["UK", "AU"] + } + ), value_type = Option)] + pub accepted_countries: Option, + + /// Minimum amount supported by the processor. To be represented in the lowest denomination of the target currency (For example, for USD it should be in cents) + #[schema(example = 1)] + pub minimum_amount: Option, + + /// Maximum amount supported by the processor. To be represented in the lowest denomination of + /// the target currency (For example, for USD it should be in cents) + #[schema(example = 1313)] + pub maximum_amount: Option, + + /// Boolean to enable recurring payments / mandates. Default is true. + #[schema(default = true, example = false)] + pub recurring_enabled: bool, + + /// Boolean to enable installment / EMI / BNPL payments. Default is true. + #[schema(default = true, example = false)] + pub installment_payment_enabled: bool, +} + +#[derive(PartialEq, Eq, Hash, Debug, Clone, serde::Serialize, Deserialize, ToSchema)] +#[serde( + deny_unknown_fields, + tag = "type", + content = "list", + rename_all = "snake_case" +)] +/// Object to filter the countries for which the payment method subtype is enabled +pub enum AcceptedCountries { + /// Only enable the payment method subtype for specific countries + #[schema(value_type = Vec)] + EnableOnly(Vec), + + /// Only disable the payment method subtype for specific countries + #[schema(value_type = Vec)] + DisableOnly(Vec), + + /// Enable the payment method subtype for all countries, in which the processor has the processing capabilities + AllAccepted, +} + +#[derive(PartialEq, Eq, Hash, Debug, Clone, serde::Serialize, Deserialize, ToSchema)] +#[serde( + deny_unknown_fields, + tag = "type", + content = "list", + rename_all = "snake_case" +)] +/// Object to filter the countries for which the payment method subtype is enabled +pub enum AcceptedCurrencies { + /// Only enable the payment method subtype for specific currencies + #[schema(value_type = Vec)] + EnableOnly(Vec), + + /// Only disable the payment method subtype for specific currencies + #[schema(value_type = Vec)] + DisableOnly(Vec), + + /// Enable the payment method subtype for all currencies, in which the processor has the processing capabilities + AllAccepted, +} + +impl Queryable for PaymentMethodsEnabled +where + DB: Backend, + Self: FromSql, +{ + type Row = Self; + + fn build(row: Self::Row) -> deserialize::Result { + Ok(row) + } +} + +common_utils::impl_to_sql_from_sql_json!(PaymentMethodsEnabled); diff --git a/crates/common_types/src/payments.rs b/crates/common_types/src/payments.rs new file mode 100644 index 000000000000..0eef7ecaf2b4 --- /dev/null +++ b/crates/common_types/src/payments.rs @@ -0,0 +1,40 @@ +//! Payment related types + +use common_enums::enums; +use common_utils::{impl_to_sql_from_sql_json, types::MinorUnit}; +use diesel::{sql_types::Jsonb, AsExpression, FromSqlRow}; +use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; + +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(rename_all = "snake_case")] +#[serde(deny_unknown_fields)] +/// Fee information for Split Payments to be charged on the payment being collected +pub enum SplitPaymentsRequest { + /// StripeSplitPayment + StripeSplitPayment(StripeSplitPaymentRequest), +} +impl_to_sql_from_sql_json!(SplitPaymentsRequest); + +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(deny_unknown_fields)] +/// Fee information for Split Payments to be charged on the payment being collected for Stripe +pub struct StripeSplitPaymentRequest { + /// Stripe's charge type + #[schema(value_type = PaymentChargeType, example = "direct")] + pub charge_type: enums::PaymentChargeType, + + /// Platform fees to be collected on the payment + #[schema(value_type = i64, example = 6540)] + pub application_fees: MinorUnit, + + /// Identifier for the reseller's account to send the funds to + pub transfer_account_id: String, +} +impl_to_sql_from_sql_json!(StripeSplitPaymentRequest); diff --git a/crates/common_types/src/refunds.rs b/crates/common_types/src/refunds.rs new file mode 100644 index 000000000000..e4b7c5a336fa --- /dev/null +++ b/crates/common_types/src/refunds.rs @@ -0,0 +1,36 @@ +//! Refund related types + +use common_utils::impl_to_sql_from_sql_json; +use diesel::{sql_types::Jsonb, AsExpression, FromSqlRow}; +use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; + +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(rename_all = "snake_case")] +#[serde(deny_unknown_fields)] +/// Charge specific fields for controlling the revert of funds from either platform or connected account. Check sub-fields for more details. +pub enum SplitRefund { + /// StripeSplitRefundRequest + StripeSplitRefund(StripeSplitRefundRequest), +} +impl_to_sql_from_sql_json!(SplitRefund); + +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(deny_unknown_fields)] +/// Charge specific fields for controlling the revert of funds from either platform or connected account for Stripe. Check sub-fields for more details. +pub struct StripeSplitRefundRequest { + /// 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_to_sql_from_sql_json!(StripeSplitRefundRequest); diff --git a/crates/common_utils/Cargo.toml b/crates/common_utils/Cargo.toml index fdc9670045e0..dcdb4c760b1b 100644 --- a/crates/common_utils/Cargo.toml +++ b/crates/common_utils/Cargo.toml @@ -8,7 +8,7 @@ readme = "README.md" license.workspace = true [features] -default = ["v1"] +default = [] keymanager = ["dep:router_env"] keymanager_mtls = ["reqwest/rustls-tls"] encryption_service = ["dep:router_env"] diff --git a/crates/common_utils/src/consts.rs b/crates/common_utils/src/consts.rs index 00ffb614bb05..3b437b703bef 100644 --- a/crates/common_utils/src/consts.rs +++ b/crates/common_utils/src/consts.rs @@ -1,7 +1,5 @@ //! Commonly used constants -use std::collections::HashSet; - /// Number of characters in a generated ID pub const ID_LENGTH: usize = 20; @@ -51,26 +49,12 @@ pub const SURCHARGE_PERCENTAGE_PRECISION_LENGTH: u8 = 2; /// Header Key for application overhead of a request pub const X_HS_LATENCY: &str = "x-hs-latency"; -/// Default Payment Link Background color -pub const DEFAULT_BACKGROUND_COLOR: &str = "#212E46"; - -/// Default product Img Link -pub const DEFAULT_PRODUCT_IMG: &str = - "https://live.hyperswitch.io/payment-link-assets/cart_placeholder.png"; - -/// Default Merchant Logo Link -pub const DEFAULT_MERCHANT_LOGO: &str = - "https://live.hyperswitch.io/payment-link-assets/Merchant_placeholder.png"; - /// Redirect url for Prophetpay pub const PROPHETPAY_REDIRECT_URL: &str = "https://ccm-thirdparty.cps.golf/hp/tokenize/"; /// Variable which store the card token for Prophetpay pub const PROPHETPAY_TOKEN: &str = "cctoken"; -/// Default SDK Layout -pub const DEFAULT_SDK_LAYOUT: &str = "tabs"; - /// Payment intent default client secret expiry (in seconds) pub const DEFAULT_SESSION_EXPIRY: i64 = 15 * 60; @@ -80,15 +64,6 @@ pub const DEFAULT_INTENT_FULFILLMENT_TIME: i64 = 15 * 60; /// Payment order fulfillment time (in seconds) pub const DEFAULT_ORDER_FULFILLMENT_TIME: i64 = 15 * 60; -/// Default bool for Display sdk only -pub const DEFAULT_DISPLAY_SDK_ONLY: bool = false; - -/// Default bool to enable saved payment method -pub const DEFAULT_ENABLE_SAVED_PAYMENT_METHOD: bool = false; - -/// Default allowed domains for payment links -pub const DEFAULT_ALLOWED_DOMAINS: Option> = None; - /// Default ttl for Extended card info in redis (in seconds) pub const DEFAULT_TTL_FOR_EXTENDED_CARD_INFO: u16 = 15 * 60; @@ -118,6 +93,10 @@ pub const CELL_IDENTIFIER_LENGTH: u8 = 5; /// General purpose base64 engine pub const BASE64_ENGINE: base64::engine::GeneralPurpose = base64::engine::general_purpose::STANDARD; + +/// URL Safe base64 engine +pub const BASE64_ENGINE_URL_SAFE: base64::engine::GeneralPurpose = + base64::engine::general_purpose::URL_SAFE; /// Regex for matching a domain /// Eg - /// http://www.example.com @@ -140,6 +119,8 @@ pub const MAX_ALLOWED_MERCHANT_NAME_LENGTH: usize = 64; /// Default locale pub const DEFAULT_LOCALE: &str = "en"; +/// Role ID for Tenant Admin +pub const ROLE_ID_TENANT_ADMIN: &str = "tenant_admin"; /// Role ID for Org Admin pub const ROLE_ID_ORGANIZATION_ADMIN: &str = "org_admin"; /// Role ID for Internal View Only @@ -154,3 +135,17 @@ pub const MAX_DESCRIPTION_LENGTH: u16 = 255; pub const MAX_STATEMENT_DESCRIPTOR_LENGTH: u16 = 22; /// Payout flow identifier used for performing GSM operations pub const PAYOUT_FLOW_STR: &str = "payout_flow"; + +/// length of the publishable key +pub const PUBLISHABLE_KEY_LENGTH: u16 = 39; + +/// The number of bytes allocated for the hashed connector transaction ID. +/// Total number of characters equals CONNECTOR_TRANSACTION_ID_HASH_BYTES times 2. +pub const CONNECTOR_TRANSACTION_ID_HASH_BYTES: usize = 25; + +/// Apple Pay validation url +pub const APPLEPAY_VALIDATION_URL: &str = + "https://apple-pay-gateway-cert.apple.com/paymentservices/startSession"; + +/// Request ID +pub const X_REQUEST_ID: &str = "x-request-id"; diff --git a/crates/common_utils/src/crypto.rs b/crates/common_utils/src/crypto.rs index 8b8707d9b998..e8c2b2d5a0bf 100644 --- a/crates/common_utils/src/crypto.rs +++ b/crates/common_utils/src/crypto.rs @@ -238,7 +238,6 @@ impl VerifySignature for HmacSha512 { } } -/// /// Blake3 #[derive(Debug)] pub struct Blake3(String); @@ -437,9 +436,7 @@ pub fn generate_cryptographically_secure_random_bytes() -> [u8; bytes } -/// /// A wrapper type to store the encrypted data for sensitive pii domain data types -/// #[derive(Debug, Clone)] pub struct Encryptable { inner: T, @@ -447,9 +444,7 @@ pub struct Encryptable { } impl> Encryptable> { - /// /// constructor function to be used by the encryptor and decryptor to generate the data type - /// pub fn new( masked_data: Secret, encrypted_data: Secret, EncryptionStrategy>, @@ -462,29 +457,39 @@ impl> Encryptable> { } impl Encryptable { - /// /// Get the inner data while consuming self - /// #[inline] pub fn into_inner(self) -> T { self.inner } - /// /// Get the reference to inner value - /// #[inline] pub fn get_inner(&self) -> &T { &self.inner } - /// /// Get the inner encrypted data while consuming self - /// #[inline] pub fn into_encrypted(self) -> Secret, EncryptionStrategy> { self.encrypted } + + /// Deserialize inner value and return new Encryptable object + pub fn deserialize_inner_value( + self, + f: F, + ) -> CustomResult, errors::ParsingError> + where + F: FnOnce(T) -> CustomResult, + U: Clone, + { + // Option::map(self, f) + let inner = self.inner; + let encrypted = self.encrypted; + let inner = f(inner)?; + Ok(Encryptable { inner, encrypted }) + } } impl Deref for Encryptable> { diff --git a/crates/common_utils/src/custom_serde.rs b/crates/common_utils/src/custom_serde.rs index 79e0c5b85e76..63ef30011f77 100644 --- a/crates/common_utils/src/custom_serde.rs +++ b/crates/common_utils/src/custom_serde.rs @@ -202,7 +202,6 @@ pub mod timestamp { } /// - pub mod json_string { use serde::de::{self, Deserialize, DeserializeOwned, Deserializer}; use serde_json; diff --git a/crates/common_utils/src/errors.rs b/crates/common_utils/src/errors.rs index 38b89cbfaf7e..e62606b45859 100644 --- a/crates/common_utils/src/errors.rs +++ b/crates/common_utils/src/errors.rs @@ -7,7 +7,6 @@ use crate::types::MinorUnit; /// error_stack::Report specific extendability /// /// Effectively, equivalent to `Result>` -/// pub type CustomResult = error_stack::Result; /// Parsing Errors diff --git a/crates/common_utils/src/events.rs b/crates/common_utils/src/events.rs index 2b1571617e8d..9494b8209e75 100644 --- a/crates/common_utils/src/events.rs +++ b/crates/common_utils/src/events.rs @@ -15,13 +15,24 @@ pub enum ApiEventsType { Payout { payout_id: String, }, + #[cfg(feature = "v1")] Payment { payment_id: id_type::PaymentId, }, + #[cfg(feature = "v2")] + Payment { + payment_id: id_type::GlobalPaymentId, + }, + #[cfg(feature = "v1")] Refund { payment_id: Option, refund_id: String, }, + #[cfg(feature = "v2")] + Refund { + payment_id: id_type::GlobalPaymentId, + refund_id: id_type::GlobalRefundId, + }, PaymentMethod { payment_method_id: String, payment_method: Option, @@ -31,7 +42,7 @@ pub enum ApiEventsType { PaymentMethodCreate, #[cfg(all(feature = "v2", feature = "customer_v2"))] Customer { - id: String, + customer_id: Option, }, #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] Customer { @@ -40,22 +51,36 @@ pub enum ApiEventsType { BusinessProfile { profile_id: id_type::ProfileId, }, + ApiKey { + key_id: id_type::ApiKeyId, + }, User { user_id: String, }, PaymentMethodList { payment_id: Option, }, + #[cfg(feature = "v1")] Webhooks { connector: String, payment_id: Option, }, + #[cfg(feature = "v2")] + Webhooks { + connector: id_type::MerchantConnectorAccountId, + payment_id: Option, + }, Routing, ResourceListAPI, + #[cfg(feature = "v1")] PaymentRedirectionResponse { connector: Option, payment_id: Option, }, + #[cfg(feature = "v2")] + PaymentRedirectionResponse { + payment_id: id_type::GlobalPaymentId, + }, Gsm, // TODO: This has to be removed once the corresponding apiEventTypes are created Miscellaneous, @@ -77,11 +102,15 @@ pub enum ApiEventsType { poll_id: String, }, Analytics, + EphemeralKey { + key_id: id_type::EphemeralKeyId, + }, } impl ApiEventMetric for serde_json::Value {} impl ApiEventMetric for () {} +#[cfg(feature = "v1")] impl ApiEventMetric for id_type::PaymentId { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { @@ -90,6 +119,15 @@ impl ApiEventMetric for id_type::PaymentId { } } +#[cfg(feature = "v2")] +impl ApiEventMetric for id_type::GlobalPaymentId { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Payment { + payment_id: self.clone(), + }) + } +} + impl ApiEventMetric for Result { fn get_api_event_type(&self) -> Option { match self { @@ -124,10 +162,6 @@ impl_api_event_type!( ( String, id_type::MerchantId, - (id_type::MerchantId, String), - (id_type::MerchantId, &String), - (&id_type::MerchantId, &String), - (&String, &String), (Option, Option, String), (Option, Option, id_type::MerchantId), bool diff --git a/crates/common_utils/src/ext_traits.rs b/crates/common_utils/src/ext_traits.rs index 5ad53ec85335..945056aafefd 100644 --- a/crates/common_utils/src/ext_traits.rs +++ b/crates/common_utils/src/ext_traits.rs @@ -1,7 +1,5 @@ -//! //! This module holds traits for extending functionalities for existing datatypes //! & inbuilt datatypes. -//! use error_stack::ResultExt; use masking::{ExposeInterface, PeekInterface, Secret, Strategy}; @@ -14,10 +12,8 @@ use crate::{ fp_utils::when, }; -/// /// Encode interface /// An interface for performing type conversions and serialization -/// pub trait Encode<'e> where Self: 'e + std::fmt::Debug, @@ -27,62 +23,49 @@ where /// Converting `Self` into an intermediate representation `

` /// and then performing encoding operation using the `Serialize` trait from `serde` /// Specifically to convert into json, by using `serde_json` - /// fn convert_and_encode

(&'e self) -> CustomResult where P: TryFrom<&'e Self> + Serialize, Result>::Error>: ResultExt, >::Error> as ResultExt>::Ok: Serialize; - /// /// Converting `Self` into an intermediate representation `

` /// and then performing encoding operation using the `Serialize` trait from `serde` /// Specifically, to convert into urlencoded, by using `serde_urlencoded` - /// fn convert_and_url_encode

(&'e self) -> CustomResult where P: TryFrom<&'e Self> + Serialize, Result>::Error>: ResultExt, >::Error> as ResultExt>::Ok: Serialize; - /// /// Functionality, for specifically encoding `Self` into `String` /// after serialization by using `serde::Serialize` - /// fn url_encode(&'e self) -> CustomResult where Self: Serialize; - /// /// Functionality, for specifically encoding `Self` into `String` /// after serialization by using `serde::Serialize` /// specifically, to convert into JSON `String`. - /// fn encode_to_string_of_json(&'e self) -> CustomResult where Self: Serialize; - /// /// Functionality, for specifically encoding `Self` into `String` /// after serialization by using `serde::Serialize` /// specifically, to convert into XML `String`. - /// fn encode_to_string_of_xml(&'e self) -> CustomResult where Self: Serialize; - /// /// Functionality, for specifically encoding `Self` into `serde_json::Value` /// after serialization by using `serde::Serialize` - /// fn encode_to_value(&'e self) -> CustomResult where Self: Serialize; - /// /// Functionality, for specifically encoding `Self` into `Vec` /// after serialization by using `serde::Serialize` - /// fn encode_to_vec(&'e self) -> CustomResult, errors::ParsingError> where Self: Serialize; @@ -165,13 +148,9 @@ where } } -/// /// Extending functionalities of `bytes::Bytes` -/// pub trait BytesExt { - /// /// Convert `bytes::Bytes` into type `` using `serde::Deserialize` - /// fn parse_struct<'de, T>( &'de self, type_name: &'static str, @@ -199,13 +178,9 @@ impl BytesExt for bytes::Bytes { } } -/// /// Extending functionalities of `[u8]` for performing parsing -/// pub trait ByteSliceExt { - /// /// Convert `[u8]` into type `` by using `serde::Deserialize` - /// fn parse_struct<'de, T>( &'de self, type_name: &'static str, @@ -229,13 +204,9 @@ impl ByteSliceExt for [u8] { } } -/// /// Extending functionalities of `serde_json::Value` for performing parsing -/// pub trait ValueExt { - /// /// Convert `serde_json::Value` into type `` by using `serde::Deserialize` - /// fn parse_value(self, type_name: &'static str) -> CustomResult where T: serde::de::DeserializeOwned; @@ -277,22 +248,16 @@ impl ValueExt for crypto::Encryptable { } } -/// /// Extending functionalities of `String` for performing parsing -/// pub trait StringExt { - /// /// Convert `String` into type `` (which being an `enum`) - /// fn parse_enum(self, enum_name: &'static str) -> CustomResult where T: std::str::FromStr, // Requirement for converting the `Err` variant of `FromStr` to `Report` ::Err: std::error::Error + Send + Sync + 'static; - /// /// Convert `serde_json::Value` into type `` by using `serde::Deserialize` - /// fn parse_struct<'de>( &'de self, type_name: &'static str, @@ -327,25 +292,20 @@ impl StringExt for String { } } -/// /// Extending functionalities of Wrapper types for idiomatic -/// #[cfg(feature = "async_ext")] #[cfg_attr(feature = "async_ext", async_trait::async_trait)] pub trait AsyncExt { /// Output type of the map function type WrappedSelf; - /// + /// Extending map by allowing functions which are async - /// async fn async_map(self, func: F) -> Self::WrappedSelf where F: FnOnce(A) -> Fut + Send, Fut: futures::Future + Send; - /// /// Extending the `and_then` by allowing functions which are async - /// async fn async_and_then(self, func: F) -> Self::WrappedSelf where F: FnOnce(A) -> Fut + Send, @@ -469,9 +429,7 @@ where /// Extension trait for deserializing XML strings using `quick-xml` crate pub trait XmlExt { - /// /// Deserialize an XML string into the specified type ``. - /// fn parse_xml(self) -> Result where T: serde::de::DeserializeOwned; diff --git a/crates/common_utils/src/hashing.rs b/crates/common_utils/src/hashing.rs index d08cd9f0868a..0982ca537881 100644 --- a/crates/common_utils/src/hashing.rs +++ b/crates/common_utils/src/hashing.rs @@ -1,7 +1,7 @@ use masking::{PeekInterface, Secret, Strategy}; use serde::{Deserialize, Serialize, Serializer}; -#[derive(Clone, Debug, Deserialize)] +#[derive(Clone, PartialEq, Debug, Deserialize)] /// Represents a hashed string using blake3's hashing strategy. pub struct HashedString>(Secret); diff --git a/crates/common_utils/src/id_type.rs b/crates/common_utils/src/id_type.rs index 78e3841690a1..ea90c00121e8 100644 --- a/crates/common_utils/src/id_type.rs +++ b/crates/common_utils/src/id_type.rs @@ -1,20 +1,23 @@ //! Common ID types //! The id type can be used to create specific id types with custom behaviour -use std::{borrow::Cow, fmt::Debug}; - +mod api_key; mod customer; +mod ephemeral_key; +#[cfg(feature = "v2")] +mod global_id; mod merchant; mod merchant_connector_account; mod organization; mod payment; mod profile; +mod refunds; +mod relay; mod routing; +mod tenant; -#[cfg(feature = "v2")] -mod global_id; +use std::{borrow::Cow, fmt::Debug}; -pub use customer::CustomerId; use diesel::{ backend::Backend, deserialize::FromSql, @@ -22,21 +25,35 @@ use diesel::{ serialize::{Output, ToSql}, sql_types, }; -#[cfg(feature = "v2")] -pub use global_id::{payment::GlobalPaymentId, payment_methods::GlobalPaymentMethodId, CellId}; -pub use merchant::MerchantId; -pub use merchant_connector_account::MerchantConnectorAccountId; -pub use organization::OrganizationId; -pub use payment::{PaymentId, PaymentReferenceId}; -pub use profile::ProfileId; -pub use routing::RoutingId; use serde::{Deserialize, Serialize}; use thiserror::Error; +#[cfg(feature = "v2")] +pub use self::global_id::{ + customer::GlobalCustomerId, + payment::{GlobalAttemptId, GlobalPaymentId}, + payment_methods::GlobalPaymentMethodId, + refunds::GlobalRefundId, + CellId, +}; +pub use self::{ + api_key::ApiKeyId, + customer::CustomerId, + ephemeral_key::EphemeralKeyId, + merchant::MerchantId, + merchant_connector_account::MerchantConnectorAccountId, + organization::OrganizationId, + payment::{PaymentId, PaymentReferenceId}, + profile::ProfileId, + refunds::RefundReferenceId, + relay::RelayId, + routing::RoutingId, + tenant::TenantId, +}; use crate::{fp_utils::when, generate_id_with_default_len}; #[inline] -fn is_valid_id_character(input_char: &char) -> bool { +fn is_valid_id_character(input_char: char) -> bool { input_char.is_ascii_alphanumeric() || matches!(input_char, '_' | '-') } @@ -46,7 +63,7 @@ fn get_invalid_input_character(input_string: Cow<'static, str>) -> Option input_string .trim() .chars() - .find(|char| !is_valid_id_character(char)) + .find(|&char| !is_valid_id_character(char)) } #[derive(Debug, PartialEq, Hash, Serialize, Clone, Eq)] diff --git a/crates/common_utils/src/id_type/api_key.rs b/crates/common_utils/src/id_type/api_key.rs new file mode 100644 index 000000000000..f252846e6ac3 --- /dev/null +++ b/crates/common_utils/src/id_type/api_key.rs @@ -0,0 +1,46 @@ +crate::id_type!( + ApiKeyId, + "A type for key_id that can be used for API key IDs" +); +crate::impl_id_type_methods!(ApiKeyId, "key_id"); + +// This is to display the `ApiKeyId` as ApiKeyId(abcd) +crate::impl_debug_id_type!(ApiKeyId); +crate::impl_try_from_cow_str_id_type!(ApiKeyId, "key_id"); + +crate::impl_serializable_secret_id_type!(ApiKeyId); +crate::impl_queryable_id_type!(ApiKeyId); +crate::impl_to_sql_from_sql_id_type!(ApiKeyId); + +impl ApiKeyId { + /// Generate Api Key Id from prefix + pub fn generate_key_id(prefix: &'static str) -> Self { + Self(crate::generate_ref_id_with_default_length(prefix)) + } +} + +impl crate::events::ApiEventMetric for ApiKeyId { + fn get_api_event_type(&self) -> Option { + Some(crate::events::ApiEventsType::ApiKey { + key_id: self.clone(), + }) + } +} + +impl crate::events::ApiEventMetric for (super::MerchantId, ApiKeyId) { + fn get_api_event_type(&self) -> Option { + Some(crate::events::ApiEventsType::ApiKey { + key_id: self.1.clone(), + }) + } +} + +impl crate::events::ApiEventMetric for (&super::MerchantId, &ApiKeyId) { + fn get_api_event_type(&self) -> Option { + Some(crate::events::ApiEventsType::ApiKey { + key_id: self.1.clone(), + }) + } +} + +crate::impl_default_id_type!(ApiKeyId, "key"); diff --git a/crates/common_utils/src/id_type/customer.rs b/crates/common_utils/src/id_type/customer.rs index 9d02f2013837..54c44020b87d 100644 --- a/crates/common_utils/src/id_type/customer.rs +++ b/crates/common_utils/src/id_type/customer.rs @@ -13,3 +13,12 @@ crate::impl_generate_id_id_type!(CustomerId, "cus"); crate::impl_serializable_secret_id_type!(CustomerId); crate::impl_queryable_id_type!(CustomerId); crate::impl_to_sql_from_sql_id_type!(CustomerId); + +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] +impl crate::events::ApiEventMetric for CustomerId { + fn get_api_event_type(&self) -> Option { + Some(crate::events::ApiEventsType::Customer { + customer_id: self.clone(), + }) + } +} diff --git a/crates/common_utils/src/id_type/ephemeral_key.rs b/crates/common_utils/src/id_type/ephemeral_key.rs new file mode 100644 index 000000000000..071980fc6a46 --- /dev/null +++ b/crates/common_utils/src/id_type/ephemeral_key.rs @@ -0,0 +1,31 @@ +crate::id_type!( + EphemeralKeyId, + "A type for key_id that can be used for Ephemeral key IDs" +); +crate::impl_id_type_methods!(EphemeralKeyId, "key_id"); + +// This is to display the `EphemeralKeyId` as EphemeralKeyId(abcd) +crate::impl_debug_id_type!(EphemeralKeyId); +crate::impl_try_from_cow_str_id_type!(EphemeralKeyId, "key_id"); + +crate::impl_generate_id_id_type!(EphemeralKeyId, "eki"); +crate::impl_serializable_secret_id_type!(EphemeralKeyId); +crate::impl_queryable_id_type!(EphemeralKeyId); +crate::impl_to_sql_from_sql_id_type!(EphemeralKeyId); + +impl crate::events::ApiEventMetric for EphemeralKeyId { + fn get_api_event_type(&self) -> Option { + Some(crate::events::ApiEventsType::EphemeralKey { + key_id: self.clone(), + }) + } +} + +crate::impl_default_id_type!(EphemeralKeyId, "key"); + +impl EphemeralKeyId { + /// Generate a key for redis + pub fn generate_redis_key(&self) -> String { + format!("epkey_{}", self.get_string_repr()) + } +} diff --git a/crates/common_utils/src/id_type/global_id.rs b/crates/common_utils/src/id_type/global_id.rs index 0709ce84d581..1ad1bd960840 100644 --- a/crates/common_utils/src/id_type/global_id.rs +++ b/crates/common_utils/src/id_type/global_id.rs @@ -1,6 +1,7 @@ -#![allow(unused)] -pub mod payment; -pub mod payment_methods; +pub(super) mod customer; +pub(super) mod payment; +pub(super) mod payment_methods; +pub(super) mod refunds; use diesel::{backend::Backend, deserialize::FromSql, serialize::ToSql, sql_types}; use error_stack::ResultExt; @@ -23,15 +24,19 @@ pub(crate) struct GlobalId(LengthId) pub(crate) enum GlobalEntity { Customer, Payment, + Attempt, PaymentMethod, + Refund, } impl GlobalEntity { - fn prefix(&self) -> &'static str { + fn prefix(self) -> &'static str { match self { Self::Customer => "cus", Self::Payment => "pay", Self::PaymentMethod => "pm", + Self::Attempt => "att", + Self::Refund => "ref", } } } @@ -116,7 +121,7 @@ pub(crate) enum GlobalIdError { impl GlobalId { /// Create a new global id from entity and cell information /// The entity prefix is used to identify the entity, `cus` for customers, `pay`` for payments etc. - pub fn generate(cell_id: CellId, entity: GlobalEntity) -> Self { + pub fn generate(cell_id: &CellId, entity: GlobalEntity) -> Self { let prefix = format!("{}_{}", cell_id.get_string_repr(), entity.prefix()); let id = generate_time_ordered_id(&prefix); let alphanumeric_id = AlphaNumericId::new_unchecked(id); @@ -197,7 +202,7 @@ mod global_id_tests { let cell_id_string = "12345"; let entity = GlobalEntity::Customer; let cell_id = CellId::from_str(cell_id_string).unwrap(); - let global_id = GlobalId::generate(cell_id, entity); + let global_id = GlobalId::generate(&cell_id, entity); /// Generate a regex for globalid /// Eg - 12abc_cus_abcdefghijklmnopqrstuvwxyz1234567890 diff --git a/crates/common_utils/src/id_type/global_id/customer.rs b/crates/common_utils/src/id_type/global_id/customer.rs new file mode 100644 index 000000000000..e0de91d8aed7 --- /dev/null +++ b/crates/common_utils/src/id_type/global_id/customer.rs @@ -0,0 +1,45 @@ +use error_stack::ResultExt; + +use crate::{errors, generate_id_with_default_len, generate_time_ordered_id_without_prefix, types}; + +crate::global_id_type!( + GlobalCustomerId, + "A global id that can be used to identify a customer. + +The format will be `__`. + +Example: `cell1_cus_uu1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p`" +); + +// Database related implementations so that this field can be used directly in the database tables +crate::impl_queryable_id_type!(GlobalCustomerId); +crate::impl_to_sql_from_sql_global_id_type!(GlobalCustomerId); + +impl GlobalCustomerId { + /// Get string representation of the id + pub fn get_string_repr(&self) -> &str { + self.0.get_string_repr() + } + + /// Generate a new GlobalCustomerId from a cell id + pub fn generate(cell_id: &crate::id_type::CellId) -> Self { + let global_id = super::GlobalId::generate(cell_id, super::GlobalEntity::Customer); + Self(global_id) + } +} + +impl TryFrom for crate::id_type::CustomerId { + type Error = error_stack::Report; + + fn try_from(value: GlobalCustomerId) -> Result { + Self::try_from(std::borrow::Cow::from(value.get_string_repr().to_owned())) + } +} + +impl crate::events::ApiEventMetric for GlobalCustomerId { + fn get_api_event_type(&self) -> Option { + Some(crate::events::ApiEventsType::Customer { + customer_id: Some(self.clone()), + }) + } +} diff --git a/crates/common_utils/src/id_type/global_id/payment.rs b/crates/common_utils/src/id_type/global_id/payment.rs index edc7e3fb9604..c65fd06f0687 100644 --- a/crates/common_utils/src/id_type/global_id/payment.rs +++ b/crates/common_utils/src/id_type/global_id/payment.rs @@ -1,27 +1,36 @@ -use crate::errors; - -/// A global id that can be used to identify a payment -#[derive( - Debug, - Clone, - Hash, - PartialEq, - Eq, - serde::Serialize, - serde::Deserialize, - diesel::expression::AsExpression, -)] -#[diesel(sql_type = diesel::sql_types::Text)] -pub struct GlobalPaymentId(super::GlobalId); +use error_stack::ResultExt; + +use crate::{errors, generate_id_with_default_len, generate_time_ordered_id_without_prefix, types}; + +crate::global_id_type!( + GlobalPaymentId, + "A global id that can be used to identify a payment. + +The format will be `__`. + +Example: `cell1_pay_uu1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p`" +); // Database related implementations so that this field can be used directly in the database tables crate::impl_queryable_id_type!(GlobalPaymentId); +crate::impl_to_sql_from_sql_global_id_type!(GlobalPaymentId); impl GlobalPaymentId { /// Get string representation of the id pub fn get_string_repr(&self) -> &str { self.0.get_string_repr() } + + /// Generate a new GlobalPaymentId from a cell id + pub fn generate(cell_id: &crate::id_type::CellId) -> Self { + let global_id = super::GlobalId::generate(cell_id, super::GlobalEntity::Payment); + Self(global_id) + } + + /// Generate a new ClientId from self + pub fn generate_client_secret(&self) -> types::ClientSecret { + types::ClientSecret::new(self.clone(), generate_time_ordered_id_without_prefix()) + } } // TODO: refactor the macro to include this id use case as well @@ -38,26 +47,37 @@ impl TryFrom> for GlobalPaymentId { } } -// TODO: refactor the macro to include this id use case as well -impl diesel::serialize::ToSql for GlobalPaymentId -where - DB: diesel::backend::Backend, - super::GlobalId: diesel::serialize::ToSql, -{ - fn to_sql<'b>( - &'b self, - out: &mut diesel::serialize::Output<'b, '_, DB>, - ) -> diesel::serialize::Result { - self.0.to_sql(out) +crate::global_id_type!( + GlobalAttemptId, + "A global id that can be used to identify a payment attempt" +); + +// Database related implementations so that this field can be used directly in the database tables +crate::impl_queryable_id_type!(GlobalAttemptId); +crate::impl_to_sql_from_sql_global_id_type!(GlobalAttemptId); + +impl GlobalAttemptId { + /// Generate a new GlobalAttemptId from a cell id + pub fn generate(cell_id: &super::CellId) -> Self { + let global_id = super::GlobalId::generate(cell_id, super::GlobalEntity::Attempt); + Self(global_id) + } + + /// Get string representation of the id + pub fn get_string_repr(&self) -> &str { + self.0.get_string_repr() } } -impl diesel::deserialize::FromSql for GlobalPaymentId -where - DB: diesel::backend::Backend, - super::GlobalId: diesel::deserialize::FromSql, -{ - fn from_sql(value: DB::RawValue<'_>) -> diesel::deserialize::Result { - super::GlobalId::from_sql(value).map(Self) +impl TryFrom> for GlobalAttemptId { + type Error = error_stack::Report; + fn try_from(value: std::borrow::Cow<'static, str>) -> Result { + use error_stack::ResultExt; + let global_attempt_id = super::GlobalId::from_string(value).change_context( + errors::ValidationError::IncorrectValueProvided { + field_name: "payment_id", + }, + )?; + Ok(Self(global_attempt_id)) } } diff --git a/crates/common_utils/src/id_type/global_id/payment_methods.rs b/crates/common_utils/src/id_type/global_id/payment_methods.rs index f6f394242cca..40bd6ec0df4a 100644 --- a/crates/common_utils/src/id_type/global_id/payment_methods.rs +++ b/crates/common_utils/src/id_type/global_id/payment_methods.rs @@ -28,10 +28,7 @@ pub enum GlobalPaymentMethodIdError { impl GlobalPaymentMethodId { /// Create a new GlobalPaymentMethodId from cell id information - pub fn generate(cell_id: &str) -> error_stack::Result { - let cell_id = CellId::from_str(cell_id) - .change_context(GlobalPaymentMethodIdError::ConstructionError) - .attach_printable("Failed to construct CellId from str")?; + pub fn generate(cell_id: &CellId) -> error_stack::Result { let global_id = GlobalId::generate(cell_id, GlobalEntity::PaymentMethod); Ok(Self(global_id)) } diff --git a/crates/common_utils/src/id_type/global_id/refunds.rs b/crates/common_utils/src/id_type/global_id/refunds.rs new file mode 100644 index 000000000000..0aac9bf5808e --- /dev/null +++ b/crates/common_utils/src/id_type/global_id/refunds.rs @@ -0,0 +1,71 @@ +use error_stack::ResultExt; + +use crate::{errors, generate_id_with_default_len, generate_time_ordered_id_without_prefix, types}; + +/// A global id that can be used to identify a refund +#[derive( + Debug, + Clone, + Hash, + PartialEq, + Eq, + serde::Serialize, + serde::Deserialize, + diesel::expression::AsExpression, +)] +#[diesel(sql_type = diesel::sql_types::Text)] +pub struct GlobalRefundId(super::GlobalId); + +// Database related implementations so that this field can be used directly in the database tables +crate::impl_queryable_id_type!(GlobalRefundId); + +impl GlobalRefundId { + /// Get string representation of the id + pub fn get_string_repr(&self) -> &str { + self.0.get_string_repr() + } + + /// Generate a new GlobalRefundId from a cell id + pub fn generate(cell_id: &crate::id_type::CellId) -> Self { + let global_id = super::GlobalId::generate(cell_id, super::GlobalEntity::Refund); + Self(global_id) + } +} + +// TODO: refactor the macro to include this id use case as well +impl TryFrom> for GlobalRefundId { + type Error = error_stack::Report; + fn try_from(value: std::borrow::Cow<'static, str>) -> Result { + use error_stack::ResultExt; + let merchant_ref_id = super::GlobalId::from_string(value).change_context( + errors::ValidationError::IncorrectValueProvided { + field_name: "refund_id", + }, + )?; + Ok(Self(merchant_ref_id)) + } +} + +// TODO: refactor the macro to include this id use case as well +impl diesel::serialize::ToSql for GlobalRefundId +where + DB: diesel::backend::Backend, + super::GlobalId: diesel::serialize::ToSql, +{ + fn to_sql<'b>( + &'b self, + out: &mut diesel::serialize::Output<'b, '_, DB>, + ) -> diesel::serialize::Result { + self.0.to_sql(out) + } +} + +impl diesel::deserialize::FromSql for GlobalRefundId +where + DB: diesel::backend::Backend, + super::GlobalId: diesel::deserialize::FromSql, +{ + fn from_sql(value: DB::RawValue<'_>) -> diesel::deserialize::Result { + super::GlobalId::from_sql(value).map(Self) + } +} diff --git a/crates/common_utils/src/id_type/merchant.rs b/crates/common_utils/src/id_type/merchant.rs index 08b80249ae34..e12f71e917f9 100644 --- a/crates/common_utils/src/id_type/merchant.rs +++ b/crates/common_utils/src/id_type/merchant.rs @@ -30,12 +30,11 @@ crate::impl_serializable_secret_id_type!(MerchantId); crate::impl_queryable_id_type!(MerchantId); crate::impl_to_sql_from_sql_id_type!(MerchantId); +// This is implemented so that we can use merchant id directly as attribute in metrics #[cfg(feature = "metrics")] -/// This is implemented so that we can use merchant id directly as attribute in metrics impl From for router_env::opentelemetry::Value { fn from(val: MerchantId) -> Self { - let string_value = val.0 .0 .0; - Self::String(router_env::opentelemetry::StringValue::from(string_value)) + Self::from(val.0 .0 .0) } } diff --git a/crates/common_utils/src/id_type/organization.rs b/crates/common_utils/src/id_type/organization.rs index f88a62daa1d6..2097fbb2450a 100644 --- a/crates/common_utils/src/id_type/organization.rs +++ b/crates/common_utils/src/id_type/organization.rs @@ -1,3 +1,5 @@ +use crate::errors::{CustomResult, ValidationError}; + crate::id_type!( OrganizationId, "A type for organization_id that can be used for organization ids" @@ -13,3 +15,10 @@ crate::impl_generate_id_id_type!(OrganizationId, "org"); crate::impl_serializable_secret_id_type!(OrganizationId); crate::impl_queryable_id_type!(OrganizationId); crate::impl_to_sql_from_sql_id_type!(OrganizationId); + +impl OrganizationId { + /// Get an organization id from String + pub fn try_from_string(org_id: String) -> CustomResult { + Self::try_from(std::borrow::Cow::from(org_id)) + } +} diff --git a/crates/common_utils/src/id_type/payment.rs b/crates/common_utils/src/id_type/payment.rs index d82a697b082e..33bf9d241707 100644 --- a/crates/common_utils/src/id_type/payment.rs +++ b/crates/common_utils/src/id_type/payment.rs @@ -75,11 +75,14 @@ crate::impl_id_type_methods!(PaymentReferenceId, "payment_reference_id"); crate::impl_debug_id_type!(PaymentReferenceId); crate::impl_try_from_cow_str_id_type!(PaymentReferenceId, "payment_reference_id"); +// Database related implementations so that this field can be used directly in the database tables +crate::impl_queryable_id_type!(PaymentReferenceId); +crate::impl_to_sql_from_sql_id_type!(PaymentReferenceId); + +// This is implemented so that we can use payment id directly as attribute in metrics #[cfg(feature = "metrics")] -/// This is implemented so that we can use payment id directly as attribute in metrics impl From for router_env::opentelemetry::Value { fn from(val: PaymentId) -> Self { - let string_value = val.0 .0 .0; - Self::String(router_env::opentelemetry::StringValue::from(string_value)) + Self::from(val.0 .0 .0) } } diff --git a/crates/common_utils/src/id_type/profile.rs b/crates/common_utils/src/id_type/profile.rs index e9d90e7bba10..9e1733a84143 100644 --- a/crates/common_utils/src/id_type/profile.rs +++ b/crates/common_utils/src/id_type/profile.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + crate::id_type!( ProfileId, "A type for profile_id that can be used for business profile ids" @@ -20,3 +22,20 @@ impl crate::events::ApiEventMetric for ProfileId { }) } } + +impl FromStr for ProfileId { + type Err = error_stack::Report; + + fn from_str(s: &str) -> Result { + let cow_string = std::borrow::Cow::Owned(s.to_string()); + Self::try_from(cow_string) + } +} + +// This is implemented so that we can use profile id directly as attribute in metrics +#[cfg(feature = "metrics")] +impl From for router_env::opentelemetry::Value { + fn from(val: ProfileId) -> Self { + Self::from(val.0 .0 .0) + } +} diff --git a/crates/common_utils/src/id_type/refunds.rs b/crates/common_utils/src/id_type/refunds.rs new file mode 100644 index 000000000000..478ff03380b5 --- /dev/null +++ b/crates/common_utils/src/id_type/refunds.rs @@ -0,0 +1,10 @@ +crate::id_type!(RefundReferenceId, "A type for refund_reference_id"); +crate::impl_id_type_methods!(RefundReferenceId, "refund_reference_id"); + +// This is to display the `RefundReferenceId` as RefundReferenceId(abcd) +crate::impl_debug_id_type!(RefundReferenceId); +crate::impl_try_from_cow_str_id_type!(RefundReferenceId, "refund_reference_id"); + +// Database related implementations so that this field can be used directly in the database tables +crate::impl_queryable_id_type!(RefundReferenceId); +crate::impl_to_sql_from_sql_id_type!(RefundReferenceId); diff --git a/crates/common_utils/src/id_type/relay.rs b/crates/common_utils/src/id_type/relay.rs new file mode 100644 index 000000000000..3ad64729fb73 --- /dev/null +++ b/crates/common_utils/src/id_type/relay.rs @@ -0,0 +1,13 @@ +crate::id_type!( + RelayId, + "A type for relay_id that can be used for relay ids" +); +crate::impl_id_type_methods!(RelayId, "relay_id"); + +crate::impl_try_from_cow_str_id_type!(RelayId, "relay_id"); +crate::impl_generate_id_id_type!(RelayId, "relay"); +crate::impl_serializable_secret_id_type!(RelayId); +crate::impl_queryable_id_type!(RelayId); +crate::impl_to_sql_from_sql_id_type!(RelayId); + +crate::impl_debug_id_type!(RelayId); diff --git a/crates/common_utils/src/id_type/tenant.rs b/crates/common_utils/src/id_type/tenant.rs new file mode 100644 index 000000000000..953bf82287af --- /dev/null +++ b/crates/common_utils/src/id_type/tenant.rs @@ -0,0 +1,22 @@ +use crate::errors::{CustomResult, ValidationError}; + +crate::id_type!( + TenantId, + "A type for tenant_id that can be used for unique identifier for a tenant" +); +crate::impl_id_type_methods!(TenantId, "tenant_id"); + +// This is to display the `TenantId` as TenantId(abcd) +crate::impl_debug_id_type!(TenantId); +crate::impl_try_from_cow_str_id_type!(TenantId, "tenant_id"); + +crate::impl_serializable_secret_id_type!(TenantId); +crate::impl_queryable_id_type!(TenantId); +crate::impl_to_sql_from_sql_id_type!(TenantId); + +impl TenantId { + /// Get tenant id from String + pub fn try_from_string(tenant_id: String) -> CustomResult { + Self::try_from(std::borrow::Cow::from(tenant_id)) + } +} diff --git a/crates/common_utils/src/lib.rs b/crates/common_utils/src/lib.rs index ecffa259bc39..463ec3ee1b67 100644 --- a/crates/common_utils/src/lib.rs +++ b/crates/common_utils/src/lib.rs @@ -264,6 +264,17 @@ pub fn generate_time_ordered_id(prefix: &str) -> String { format!("{prefix}_{}", uuid::Uuid::now_v7().as_simple()) } +/// Generate a time-ordered (time-sortable) unique identifier using the current time without prefix +#[inline] +pub fn generate_time_ordered_id_without_prefix() -> String { + uuid::Uuid::now_v7().as_simple().to_string() +} + +/// Generate a nanoid with the specified length +#[inline] +pub fn generate_id_with_len(length: usize) -> String { + nanoid::nanoid!(length, &consts::ALPHABETS) +} #[allow(missing_docs)] pub trait DbConnectionParams { fn get_username(&self) -> &str; diff --git a/crates/common_utils/src/macros.rs b/crates/common_utils/src/macros.rs index 94d8074c3014..fe1289acba03 100644 --- a/crates/common_utils/src/macros.rs +++ b/crates/common_utils/src/macros.rs @@ -172,6 +172,27 @@ mod id_type { }; } + /// Defines a Global Id type + #[cfg(feature = "v2")] + #[macro_export] + macro_rules! global_id_type { + ($type:ident, $doc:literal) => { + #[doc = $doc] + #[derive( + Debug, + Clone, + Hash, + PartialEq, + Eq, + serde::Serialize, + serde::Deserialize, + diesel::expression::AsExpression, + )] + #[diesel(sql_type = diesel::sql_types::Text)] + pub struct $type($crate::id_type::global_id::GlobalId); + }; + } + /// Implements common methods on the specified ID type. #[macro_export] macro_rules! impl_id_type_methods { @@ -292,6 +313,40 @@ mod id_type { }; } + #[cfg(feature = "v2")] + /// Implements the `ToSql` and `FromSql` traits on the specified Global ID type. + #[macro_export] + macro_rules! impl_to_sql_from_sql_global_id_type { + ($type:ty, $diesel_type:ty) => { + impl diesel::serialize::ToSql<$diesel_type, DB> for $type + where + DB: diesel::backend::Backend, + $crate::id_type::global_id::GlobalId: diesel::serialize::ToSql<$diesel_type, DB>, + { + fn to_sql<'b>( + &'b self, + out: &mut diesel::serialize::Output<'b, '_, DB>, + ) -> diesel::serialize::Result { + self.0.to_sql(out) + } + } + + impl diesel::deserialize::FromSql<$diesel_type, DB> for $type + where + DB: diesel::backend::Backend, + $crate::id_type::global_id::GlobalId: + diesel::deserialize::FromSql<$diesel_type, DB>, + { + fn from_sql(value: DB::RawValue<'_>) -> diesel::deserialize::Result { + $crate::id_type::global_id::GlobalId::from_sql(value).map(Self) + } + } + }; + ($type:ty) => { + $crate::impl_to_sql_from_sql_global_id_type!($type, diesel::sql_types::Text); + }; + } + /// Implements the `Queryable` trait on the specified ID type. #[macro_export] macro_rules! impl_queryable_id_type { @@ -314,6 +369,41 @@ mod id_type { } } +/// Create new generic list wrapper +#[macro_export] +macro_rules! create_list_wrapper { + ( + $wrapper_name:ident, + $type_name: ty, + impl_functions: { + $($function_def: tt)* + } + ) => { + pub struct $wrapper_name(Vec<$type_name>); + impl $wrapper_name { + pub fn new(list: Vec<$type_name>) -> Self { + Self(list) + } + pub fn iter(&self) -> std::slice::Iter<'_, $type_name> { + self.0.iter() + } + $($function_def)* + } + impl Iterator for $wrapper_name { + type Item = $type_name; + fn next(&mut self) -> Option { + self.0.pop() + } + } + + impl FromIterator<$type_name> for $wrapper_name { + fn from_iter>(iter: T) -> Self { + Self(iter.into_iter().collect()) + } + } + }; +} + /// Get the type name for a type #[macro_export] macro_rules! type_name { diff --git a/crates/common_utils/src/metrics/utils.rs b/crates/common_utils/src/metrics/utils.rs index 71244ecc4fe4..c1f2ef8f985b 100644 --- a/crates/common_utils/src/metrics/utils.rs +++ b/crates/common_utils/src/metrics/utils.rs @@ -21,13 +21,12 @@ where pub async fn record_operation_time( future: F, metric: &opentelemetry::metrics::Histogram, - metric_context: &opentelemetry::Context, key_value: &[opentelemetry::KeyValue], ) -> R where F: futures::Future, { let (result, time) = time_future(future).await; - metric.record(metric_context, time.as_secs_f64(), key_value); + metric.record(time.as_secs_f64(), key_value); result } diff --git a/crates/common_utils/src/pii.rs b/crates/common_utils/src/pii.rs index 9d4b96200ba7..5fd0e8078a25 100644 --- a/crates/common_utils/src/pii.rs +++ b/crates/common_utils/src/pii.rs @@ -348,7 +348,6 @@ where } /// Strategy for masking UPI VPA's - #[derive(Debug)] pub enum UpiVpaMaskingStrategy {} diff --git a/crates/common_utils/src/signals.rs b/crates/common_utils/src/signals.rs index 5bde366bf3c5..d008aef290e8 100644 --- a/crates/common_utils/src/signals.rs +++ b/crates/common_utils/src/signals.rs @@ -6,10 +6,8 @@ use futures::StreamExt; use router_env::logger; use tokio::sync::mpsc; -/// /// This functions is meant to run in parallel to the application. /// It will send a signal to the receiver when a SIGTERM or SIGINT is received -/// #[cfg(not(target_os = "windows"))] pub async fn signal_handler(mut sig: signal_hook_tokio::Signals, sender: mpsc::Sender<()>) { if let Some(signal) = sig.next().await { @@ -34,47 +32,35 @@ pub async fn signal_handler(mut sig: signal_hook_tokio::Signals, sender: mpsc::S } } -/// /// This functions is meant to run in parallel to the application. /// It will send a signal to the receiver when a SIGTERM or SIGINT is received -/// #[cfg(target_os = "windows")] pub async fn signal_handler(_sig: DummySignal, _sender: mpsc::Sender<()>) {} -/// /// This function is used to generate a list of signals that the signal_handler should listen for -/// #[cfg(not(target_os = "windows"))] pub fn get_allowed_signals() -> Result { signal_hook_tokio::Signals::new([signal_hook::consts::SIGTERM, signal_hook::consts::SIGINT]) } -/// /// This function is used to generate a list of signals that the signal_handler should listen for -/// #[cfg(target_os = "windows")] pub fn get_allowed_signals() -> Result { Ok(DummySignal) } -/// /// Dummy Signal Handler for windows -/// #[cfg(target_os = "windows")] #[derive(Debug, Clone)] pub struct DummySignal; #[cfg(target_os = "windows")] impl DummySignal { - /// /// Dummy handler for signals in windows (empty) - /// pub fn handle(&self) -> Self { self.clone() } - /// /// Hollow implementation, for windows compatibility - /// pub fn close(self) {} } diff --git a/crates/common_utils/src/types.rs b/crates/common_utils/src/types.rs index 0cad88bfd4f4..9e71ca76733f 100644 --- a/crates/common_utils/src/types.rs +++ b/crates/common_utils/src/types.rs @@ -3,11 +3,14 @@ pub mod keymanager; /// Enum for Authentication Level pub mod authentication; +/// Enum for Theme Lineage +pub mod theme; use std::{ borrow::Cow, fmt::Display, - ops::{Add, Sub}, + iter::Sum, + ops::{Add, Mul, Sub}, primitive::i64, str::FromStr, }; @@ -34,7 +37,9 @@ use time::PrimitiveDateTime; use utoipa::ToSchema; use crate::{ - consts::{self, MAX_DESCRIPTION_LENGTH, MAX_STATEMENT_DESCRIPTOR_LENGTH}, + consts::{ + self, MAX_DESCRIPTION_LENGTH, MAX_STATEMENT_DESCRIPTOR_LENGTH, PUBLISHABLE_KEY_LENGTH, + }, errors::{CustomResult, ParsingError, PercentageError, ValidationError}, fp_utils::when, }; @@ -329,7 +334,6 @@ impl AmountConvertor for FloatMajorUnitForConnector { } /// Connector required amount type - #[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, Copy, PartialEq)] pub struct MinorUnitForConnector; @@ -371,7 +375,7 @@ pub struct MinorUnit(i64); impl MinorUnit { /// gets amount as i64 value will be removed in future - pub fn get_amount_as_i64(&self) -> i64 { + pub fn get_amount_as_i64(self) -> i64 { self.0 } @@ -483,8 +487,21 @@ impl Sub for MinorUnit { } } -/// Connector specific types to send +impl Mul for MinorUnit { + type Output = Self; + + fn mul(self, a2: u16) -> Self::Output { + Self(self.0 * i64::from(a2)) + } +} +impl Sum for MinorUnit { + fn sum>(iter: I) -> Self { + iter.fold(Self(0), |a, b| a + b) + } +} + +/// Connector specific types to send #[derive(Default, Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq)] pub struct StringMinorUnit(String); @@ -580,7 +597,10 @@ impl StringMajorUnit { .ok_or(ParsingError::DecimalToI64ConversionFailure)?; Ok(MinorUnit::new(amount_i64)) } - + /// forms a new StringMajorUnit default unit i.e zero + pub fn zero() -> Self { + Self("0".to_string()) + } /// Get string amount from struct to be removed in future pub fn get_amount_as_string(&self) -> String { self.0.clone() @@ -603,6 +623,34 @@ impl StringMajorUnit { /// This domain type can be used for any url pub struct Url(url::Url); +impl Url { + /// Get string representation of the url + pub fn get_string_repr(&self) -> &str { + self.0.as_str() + } + + /// wrap the url::Url in Url type + pub fn wrap(url: url::Url) -> Self { + Self(url) + } + + /// Get the inner url + pub fn into_inner(self) -> url::Url { + self.0 + } + + /// Add query params to the url + pub fn add_query_params(mut self, (key, value): (&str, &str)) -> Self { + let url = self + .0 + .query_pairs_mut() + .append_pair(key, value) + .finish() + .clone(); + Self(url) + } +} + impl ToSql for Url where DB: Backend, @@ -633,6 +681,7 @@ mod client_secret_type { use std::fmt; use masking::PeekInterface; + use router_env::logger; use super::*; use crate::id_type; @@ -656,6 +705,38 @@ mod client_secret_type { self.secret.peek() ) } + + /// Create a new client secret + pub(crate) fn new(payment_id: id_type::GlobalPaymentId, secret: String) -> Self { + Self { + payment_id, + secret: masking::Secret::new(secret), + } + } + } + + impl FromStr for ClientSecret { + type Err = ParsingError; + + fn from_str(str_value: &str) -> Result { + let (payment_id, secret) = + str_value + .rsplit_once("_secret_") + .ok_or(ParsingError::EncodeError( + "Expected a string in the format '{payment_id}_secret_{secret}'", + ))?; + + let payment_id = id_type::GlobalPaymentId::try_from(Cow::Owned(payment_id.to_owned())) + .map_err(|err| { + logger::error!(global_payment_id_error=?err); + ParsingError::EncodeError("Error while constructing GlobalPaymentId") + })?; + + Ok(Self { + payment_id, + secret: masking::Secret::new(secret.to_owned()), + }) + } } impl<'de> Deserialize<'de> for ClientSecret { @@ -665,7 +746,7 @@ mod client_secret_type { { struct ClientSecretVisitor; - impl<'de> Visitor<'de> for ClientSecretVisitor { + impl Visitor<'_> for ClientSecretVisitor { type Value = ClientSecret; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -730,7 +811,23 @@ mod client_secret_type { { fn from_sql(value: DB::RawValue<'_>) -> deserialize::Result { let string_repr = String::from_sql(value)?; - Ok(serde_json::from_str(&string_repr)?) + let (payment_id, secret) = + string_repr + .rsplit_once("_secret_") + .ok_or(ParsingError::EncodeError( + "Expected a string in the format '{payment_id}_secret_{secret}'", + ))?; + + let payment_id = id_type::GlobalPaymentId::try_from(Cow::Owned(payment_id.to_owned())) + .map_err(|err| { + logger::error!(global_payment_id_error=?err); + ParsingError::EncodeError("Error while constructing GlobalPaymentId") + })?; + + Ok(Self { + payment_id, + secret: masking::Secret::new(secret.to_owned()), + }) } } @@ -745,7 +842,7 @@ mod client_secret_type { Ok(row) } } - + crate::impl_serializable_secret_id_type!(ClientSecret); #[cfg(test)] mod client_secret_tests { #![allow(clippy::expect_used)] @@ -977,7 +1074,7 @@ crate::impl_to_sql_from_sql_json!(ChargeRefunds); /// A common type of domain type that can be used for fields that contain a string with restriction of length #[derive(Debug, Clone, Serialize, Hash, PartialEq, Eq, AsExpression)] #[diesel(sql_type = sql_types::Text)] -pub(crate) struct LengthString(String); +pub(crate) struct LengthString(String); /// Error generated from violation of constraints for MerchantReferenceId #[derive(Debug, Error, PartialEq, Eq)] @@ -988,10 +1085,10 @@ pub(crate) enum LengthStringError { #[error("the minimum required length for this field is {0}")] /// Minimum length of string violated - MinLengthViolated(u8), + MinLengthViolated(u16), } -impl LengthString { +impl LengthString { /// Generates new [MerchantReferenceId] from the given input string pub fn from(input_string: Cow<'static, str>) -> Result { let trimmed_input_string = input_string.trim().to_string(); @@ -1002,7 +1099,7 @@ impl LengthString LengthString Deserialize<'de> +impl<'de, const MAX_LENGTH: u16, const MIN_LENGTH: u16> Deserialize<'de> for LengthString { fn deserialize(deserializer: D) -> Result @@ -1026,7 +1123,7 @@ impl<'de, const MAX_LENGTH: u16, const MIN_LENGTH: u8> Deserialize<'de> } } -impl FromSql +impl FromSql for LengthString where DB: Backend, @@ -1038,7 +1135,7 @@ where } } -impl ToSql +impl ToSql for LengthString where DB: Backend, @@ -1049,7 +1146,7 @@ where } } -impl Queryable +impl Queryable for LengthString where DB: Backend, @@ -1071,6 +1168,12 @@ impl Description { pub fn from_str_unchecked(input_str: &'static str) -> Self { Self(LengthString::new_unchecked(input_str.to_owned())) } + + // TODO: Remove this function in future once description in router data is updated to domain type + /// Get the string representation of the description + pub fn get_string_repr(&self) -> &str { + &self.0 .0 + } } /// Domain type for Statement Descriptor @@ -1247,3 +1350,241 @@ where self.0.to_sql(out) } } + +#[cfg(feature = "v2")] +/// Browser information to be used for 3DS 2.0 +// If any of the field is PII, then we can make them as secret +#[derive( + ToSchema, + Debug, + Clone, + serde::Deserialize, + serde::Serialize, + Eq, + PartialEq, + diesel::AsExpression, +)] +#[diesel(sql_type = Jsonb)] +pub struct BrowserInformation { + /// Color depth supported by the browser + pub color_depth: Option, + + /// Whether java is enabled in the browser + pub java_enabled: Option, + + /// Whether javascript is enabled in the browser + pub java_script_enabled: Option, + + /// Language supported + pub language: Option, + + /// The screen height in pixels + pub screen_height: Option, + + /// The screen width in pixels + pub screen_width: Option, + + /// Time zone of the client + pub time_zone: Option, + + /// Ip address of the client + #[schema(value_type = Option)] + pub ip_address: Option, + + /// List of headers that are accepted + #[schema( + example = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8" + )] + pub accept_header: Option, + + /// User-agent of the browser + pub user_agent: Option, + + /// The os type of the client device + pub os_type: Option, + + /// The os version of the client device + pub os_version: Option, + + /// The device model of the client + pub device_model: Option, +} + +#[cfg(feature = "v2")] +crate::impl_to_sql_from_sql_json!(BrowserInformation); +/// Domain type for connector_transaction_id +/// Maximum length for connector's transaction_id can be 128 characters in HS DB. +/// In case connector's use an identifier whose length exceeds 128 characters, +/// the hash value of such identifiers will be stored as connector_transaction_id. +/// The actual connector's identifier will be stored in a separate column - +/// connector_transaction_data or something with a similar name. +#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize, AsExpression)] +#[diesel(sql_type = sql_types::Text)] +pub enum ConnectorTransactionId { + /// Actual transaction identifier + TxnId(String), + /// Hashed value of the transaction identifier + HashedData(String), +} + +impl ConnectorTransactionId { + /// Implementation for retrieving the inner identifier + pub fn get_id(&self) -> &String { + match self { + Self::TxnId(id) | Self::HashedData(id) => id, + } + } + + /// Implementation for forming ConnectorTransactionId and an optional string to be used for connector_transaction_id and connector_transaction_data + pub fn form_id_and_data(src: String) -> (Self, Option) { + let txn_id = Self::from(src.clone()); + match txn_id { + Self::TxnId(_) => (txn_id, None), + Self::HashedData(_) => (txn_id, Some(src)), + } + } + + /// Implementation for retrieving + pub fn get_txn_id<'a>( + &'a self, + txn_data: Option<&'a String>, + ) -> Result<&'a String, error_stack::Report> { + match (self, txn_data) { + (Self::TxnId(id), _) => Ok(id), + (Self::HashedData(_), Some(id)) => Ok(id), + (Self::HashedData(id), None) => Err(report!(ValidationError::InvalidValue { + message: "connector_transaction_data is empty for HashedData variant".to_string(), + }) + .attach_printable(format!( + "connector_transaction_data is empty for connector_transaction_id {}", + id + ))), + } + } +} + +impl From for ConnectorTransactionId { + fn from(src: String) -> Self { + // ID already hashed + if src.starts_with("hs_hash_") { + Self::HashedData(src) + // Hash connector's transaction ID + } else if src.len() > 128 { + let mut hasher = blake3::Hasher::new(); + let mut output = [0u8; consts::CONNECTOR_TRANSACTION_ID_HASH_BYTES]; + hasher.update(src.as_bytes()); + hasher.finalize_xof().fill(&mut output); + let hash = hex::encode(output); + Self::HashedData(format!("hs_hash_{}", hash)) + // Default + } else { + Self::TxnId(src) + } + } +} + +impl Queryable for ConnectorTransactionId +where + DB: Backend, + Self: FromSql, +{ + type Row = Self; + + fn build(row: Self::Row) -> deserialize::Result { + Ok(row) + } +} + +impl FromSql for ConnectorTransactionId +where + DB: Backend, + String: FromSql, +{ + fn from_sql(bytes: DB::RawValue<'_>) -> deserialize::Result { + let val = String::from_sql(bytes)?; + Ok(Self::from(val)) + } +} + +impl ToSql for ConnectorTransactionId +where + DB: Backend, + String: ToSql, +{ + fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> diesel::serialize::Result { + match self { + Self::HashedData(id) | Self::TxnId(id) => id.to_sql(out), + } + } +} + +/// Trait for fetching actual or hashed transaction IDs +pub trait ConnectorTransactionIdTrait { + /// Returns an optional connector transaction ID + fn get_optional_connector_transaction_id(&self) -> Option<&String> { + None + } + /// Returns a connector transaction ID + fn get_connector_transaction_id(&self) -> &String { + self.get_optional_connector_transaction_id() + .unwrap_or_else(|| { + static EMPTY_STRING: String = String::new(); + &EMPTY_STRING + }) + } + /// Returns an optional connector refund ID + fn get_optional_connector_refund_id(&self) -> Option<&String> { + self.get_optional_connector_transaction_id() + } +} + +/// Domain type for PublishableKey +#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize, AsExpression)] +#[diesel(sql_type = sql_types::Text)] +pub struct PublishableKey(LengthString); + +impl PublishableKey { + /// Create a new PublishableKey Domain type without any length check from a static str + pub fn generate(env_prefix: &'static str) -> Self { + let publishable_key_string = format!("pk_{env_prefix}_{}", uuid::Uuid::now_v7().simple()); + Self(LengthString::new_unchecked(publishable_key_string)) + } + + /// Get the string representation of the PublishableKey + pub fn get_string_repr(&self) -> &str { + &self.0 .0 + } +} + +impl Queryable for PublishableKey +where + DB: Backend, + Self: FromSql, +{ + type Row = Self; + + fn build(row: Self::Row) -> deserialize::Result { + Ok(row) + } +} + +impl FromSql for PublishableKey +where + DB: Backend, + LengthString: FromSql, +{ + fn from_sql(bytes: DB::RawValue<'_>) -> deserialize::Result { + let val = LengthString::::from_sql(bytes)?; + Ok(Self(val)) + } +} + +impl ToSql for PublishableKey +where + DB: Backend, + LengthString: ToSql, +{ + fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> diesel::serialize::Result { + self.0.to_sql(out) + } +} diff --git a/crates/common_utils/src/types/keymanager.rs b/crates/common_utils/src/types/keymanager.rs index 078f1f3fcd81..09d26bd91ef8 100644 --- a/crates/common_utils/src/types/keymanager.rs +++ b/crates/common_utils/src/types/keymanager.rs @@ -393,7 +393,7 @@ impl<'de> Deserialize<'de> for DecryptedData { { struct DecryptedDataVisitor; - impl<'de> Visitor<'de> for DecryptedDataVisitor { + impl Visitor<'_> for DecryptedDataVisitor { type Value = DecryptedData; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -449,7 +449,7 @@ impl<'de> Deserialize<'de> for EncryptedData { { struct EncryptedDataVisitor; - impl<'de> Visitor<'de> for EncryptedDataVisitor { + impl Visitor<'_> for EncryptedDataVisitor { type Value = EncryptedData; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/crates/common_utils/src/types/theme.rs b/crates/common_utils/src/types/theme.rs new file mode 100644 index 000000000000..05ae26b565a7 --- /dev/null +++ b/crates/common_utils/src/types/theme.rs @@ -0,0 +1,187 @@ +use common_enums::EntityType; +use serde::{Deserialize, Serialize}; + +use crate::{ + events::{ApiEventMetric, ApiEventsType}, + id_type, impl_api_event_type, +}; + +/// Enum for having all the required lineage for every level. +/// Currently being used for theme related APIs and queries. +#[derive(Debug, Deserialize, Serialize)] +#[serde(tag = "entity_type", rename_all = "snake_case")] +pub enum ThemeLineage { + /// Tenant lineage variant + Tenant { + /// tenant_id: TenantId + tenant_id: id_type::TenantId, + }, + /// Org lineage variant + Organization { + /// tenant_id: TenantId + tenant_id: id_type::TenantId, + /// org_id: OrganizationId + org_id: id_type::OrganizationId, + }, + /// Merchant lineage variant + Merchant { + /// tenant_id: TenantId + tenant_id: id_type::TenantId, + /// org_id: OrganizationId + org_id: id_type::OrganizationId, + /// merchant_id: MerchantId + merchant_id: id_type::MerchantId, + }, + /// Profile lineage variant + Profile { + /// tenant_id: TenantId + tenant_id: id_type::TenantId, + /// org_id: OrganizationId + org_id: id_type::OrganizationId, + /// merchant_id: MerchantId + merchant_id: id_type::MerchantId, + /// profile_id: ProfileId + profile_id: id_type::ProfileId, + }, +} + +impl_api_event_type!(Miscellaneous, (ThemeLineage)); + +impl ThemeLineage { + /// Constructor for ThemeLineage + pub fn new( + entity_type: EntityType, + tenant_id: id_type::TenantId, + org_id: id_type::OrganizationId, + merchant_id: id_type::MerchantId, + profile_id: id_type::ProfileId, + ) -> Self { + match entity_type { + EntityType::Tenant => Self::Tenant { tenant_id }, + EntityType::Organization => Self::Organization { tenant_id, org_id }, + EntityType::Merchant => Self::Merchant { + tenant_id, + org_id, + merchant_id, + }, + EntityType::Profile => Self::Profile { + tenant_id, + org_id, + merchant_id, + profile_id, + }, + } + } + + /// Get the entity_type from the lineage + pub fn entity_type(&self) -> EntityType { + match self { + Self::Tenant { .. } => EntityType::Tenant, + Self::Organization { .. } => EntityType::Organization, + Self::Merchant { .. } => EntityType::Merchant, + Self::Profile { .. } => EntityType::Profile, + } + } + + /// Get the tenant_id from the lineage + pub fn tenant_id(&self) -> &id_type::TenantId { + match self { + Self::Tenant { tenant_id } + | Self::Organization { tenant_id, .. } + | Self::Merchant { tenant_id, .. } + | Self::Profile { tenant_id, .. } => tenant_id, + } + } + + /// Get the org_id from the lineage + pub fn org_id(&self) -> Option<&id_type::OrganizationId> { + match self { + Self::Tenant { .. } => None, + Self::Organization { org_id, .. } + | Self::Merchant { org_id, .. } + | Self::Profile { org_id, .. } => Some(org_id), + } + } + + /// Get the merchant_id from the lineage + pub fn merchant_id(&self) -> Option<&id_type::MerchantId> { + match self { + Self::Tenant { .. } | Self::Organization { .. } => None, + Self::Merchant { merchant_id, .. } | Self::Profile { merchant_id, .. } => { + Some(merchant_id) + } + } + } + + /// Get the profile_id from the lineage + pub fn profile_id(&self) -> Option<&id_type::ProfileId> { + match self { + Self::Tenant { .. } | Self::Organization { .. } | Self::Merchant { .. } => None, + Self::Profile { profile_id, .. } => Some(profile_id), + } + } + + /// Get higher lineages from the current lineage + pub fn get_same_and_higher_lineages(self) -> Vec { + match &self { + Self::Tenant { .. } => vec![self], + Self::Organization { tenant_id, .. } => vec![ + Self::Tenant { + tenant_id: tenant_id.clone(), + }, + self, + ], + Self::Merchant { + tenant_id, org_id, .. + } => vec![ + Self::Tenant { + tenant_id: tenant_id.clone(), + }, + Self::Organization { + tenant_id: tenant_id.clone(), + org_id: org_id.clone(), + }, + self, + ], + Self::Profile { + tenant_id, + org_id, + merchant_id, + .. + } => vec![ + Self::Tenant { + tenant_id: tenant_id.clone(), + }, + Self::Organization { + tenant_id: tenant_id.clone(), + org_id: org_id.clone(), + }, + Self::Merchant { + tenant_id: tenant_id.clone(), + org_id: org_id.clone(), + merchant_id: merchant_id.clone(), + }, + self, + ], + } + } +} + +/// Struct for holding the theme settings for email +#[derive(Debug, Clone, Default, Deserialize, Serialize)] +pub struct EmailThemeConfig { + /// The entity name to be used in the email + pub entity_name: String, + + /// The URL of the entity logo to be used in the email + pub entity_logo_url: String, + + /// The primary color to be used in the email + pub primary_color: String, + + /// The foreground color to be used in the email + pub foreground_color: String, + + /// The background color to be used in the email + pub background_color: String, +} diff --git a/crates/connector_configs/Cargo.toml b/crates/connector_configs/Cargo.toml index 0c460a9f2b32..84be33529dbf 100644 --- a/crates/connector_configs/Cargo.toml +++ b/crates/connector_configs/Cargo.toml @@ -9,15 +9,14 @@ license.workspace = true [features] default = ["payouts", "dummy_connector"] production = [] -development = [] sandbox = [] -dummy_connector = ["api_models/dummy_connector", "development"] +dummy_connector = ["api_models/dummy_connector"] payouts = ["api_models/payouts"] -v1 = ["api_models/v1"] +v1 = ["api_models/v1", "common_utils/v1"] [dependencies] # First party crates -api_models = { version = "0.1.0", path = "../api_models", package = "api_models"} +api_models = { version = "0.1.0", path = "../api_models", package = "api_models" } common_utils = { version = "0.1.0", path = "../common_utils" } # Third party crates diff --git a/crates/connector_configs/src/common_config.rs b/crates/connector_configs/src/common_config.rs index d0becb80468f..599d22ae3f67 100644 --- a/crates/connector_configs/src/common_config.rs +++ b/crates/connector_configs/src/common_config.rs @@ -102,11 +102,15 @@ pub struct ApiModelMetaData { pub source_balance_account: Option, pub brand_id: Option, pub destination_account_number: Option, + pub dpa_id: Option, + pub dpa_name: Option, + pub locale: Option, + pub card_brands: Option>, + pub merchant_category_code: Option, } #[serde_with::skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] - pub enum KlarnaEndpoint { Europe, NorthAmerica, @@ -206,7 +210,7 @@ pub enum InputType { #[serde_with::skip_serializing_none] #[derive(Debug, Deserialize, serde::Serialize, Clone)] #[serde(rename_all = "snake_case")] -pub struct MetaDataInupt { +pub struct InputData { pub name: String, pub label: String, pub placeholder: String, diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index 0c64f134b1b1..e7d83ca14c3e 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -7,10 +7,9 @@ use api_models::{ payments, }; use serde::Deserialize; -#[cfg(any(feature = "sandbox", feature = "development", feature = "production"))] use toml; -use crate::common_config::{CardProvider, MetaDataInupt, Provider, ZenApplePay}; +use crate::common_config::{CardProvider, InputData, Provider, ZenApplePay}; #[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct Classic { @@ -73,7 +72,6 @@ pub enum ApplePayTomlConfig { #[serde_with::skip_serializing_none] #[derive(Debug, Clone, serde::Serialize, Deserialize)] - pub enum KlarnaEndpoint { Europe, NorthAmerica, @@ -83,38 +81,50 @@ pub enum KlarnaEndpoint { #[serde_with::skip_serializing_none] #[derive(Debug, Deserialize, serde::Serialize, Clone)] pub struct ConfigMerchantAdditionalDetails { - pub open_banking_recipient_data: Option, - pub account_data: Option, - pub iban: Option>, - pub bacs: Option>, - pub connector_recipient_id: Option, - pub wallet_id: Option, + pub open_banking_recipient_data: Option, + pub account_data: Option, + pub iban: Option>, + pub bacs: Option>, + pub connector_recipient_id: Option, + pub wallet_id: Option, } #[serde_with::skip_serializing_none] #[derive(Debug, Deserialize, serde::Serialize, Clone)] pub struct ConfigMetadata { - pub merchant_config_currency: Option, - pub merchant_account_id: Option, - pub account_name: Option, - pub terminal_id: Option, - pub google_pay: Option>, - pub apple_pay: Option>, - pub merchant_id: Option, - pub endpoint_prefix: Option, - pub mcc: Option, - pub merchant_country_code: Option, - pub merchant_name: Option, - pub acquirer_bin: Option, - pub acquirer_merchant_id: Option, - pub acquirer_country_code: Option, - pub three_ds_requestor_name: Option, - pub three_ds_requestor_id: Option, - pub pull_mechanism_for_external_3ds_enabled: Option, - pub klarna_region: Option, - pub source_balance_account: Option, - pub brand_id: Option, - pub destination_account_number: Option, + pub merchant_config_currency: Option, + pub merchant_account_id: Option, + pub account_name: Option, + pub terminal_id: Option, + pub google_pay: Option>, + pub apple_pay: Option>, + pub merchant_id: Option, + pub endpoint_prefix: Option, + pub mcc: Option, + pub merchant_country_code: Option, + pub merchant_name: Option, + pub acquirer_bin: Option, + pub acquirer_merchant_id: Option, + pub acquirer_country_code: Option, + pub three_ds_requestor_name: Option, + pub three_ds_requestor_id: Option, + pub pull_mechanism_for_external_3ds_enabled: Option, + pub klarna_region: Option, + pub source_balance_account: Option, + pub brand_id: Option, + pub destination_account_number: Option, + pub dpa_id: Option, + pub dpa_name: Option, + pub locale: Option, + pub card_brands: Option, + pub merchant_category_code: Option, +} + +#[serde_with::skip_serializing_none] +#[derive(Debug, Deserialize, serde::Serialize, Clone)] +pub struct ConnectorWalletDetailsConfig { + pub samsung_pay: Option>, + pub paze: Option>, } #[serde_with::skip_serializing_none] @@ -123,6 +133,7 @@ pub struct ConnectorTomlConfig { pub connector_auth: Option, pub connector_webhook_details: Option, pub metadata: Option>, + pub connector_wallets_details: Option>, pub additional_merchant_data: Option>, pub credit: Option>, pub debit: Option>, @@ -172,8 +183,10 @@ pub struct ConnectorConfig { pub bambora: Option, pub datatrans: Option, pub deutschebank: Option, + pub digitalvirgo: Option, pub dlocal: Option, pub ebanx_payout: Option, + pub elavon: Option, pub fiserv: Option, pub fiservemea: Option, pub fiuu: Option, @@ -183,11 +196,14 @@ pub struct ConnectorConfig { pub gocardless: Option, pub gpayments: Option, pub helcim: Option, + // pub inespay: Option, + pub jpmorgan: Option, pub klarna: Option, pub mifinity: Option, pub mollie: Option, pub multisafepay: Option, pub nexinets: Option, + pub nexixpay: Option, pub nmi: Option, pub noon: Option, pub novalnet: Option, @@ -222,6 +238,7 @@ pub struct ConnectorConfig { pub wise_payout: Option, pub worldline: Option, pub worldpay: Option, + pub xendit: Option, pub square: Option, pub stax: Option, pub dummy_connector: Option, @@ -230,29 +247,20 @@ pub struct ConnectorConfig { pub zen: Option, pub zsl: Option, pub taxjar: Option, + pub ctp_mastercard: Option, + pub unified_authentication_service: Option, } impl ConnectorConfig { fn new() -> Result { - #[cfg(all( - feature = "production", - not(any(feature = "sandbox", feature = "development")) - ))] - let config = toml::from_str::(include_str!("../toml/production.toml")); - #[cfg(all( - feature = "sandbox", - not(any(feature = "production", feature = "development")) - ))] - let config = toml::from_str::(include_str!("../toml/sandbox.toml")); - #[cfg(feature = "development")] - let config = toml::from_str::(include_str!("../toml/development.toml")); - - #[cfg(not(any(feature = "sandbox", feature = "development", feature = "production")))] - return Err(String::from( - "Atleast one features has to be enabled for connectorconfig", - )); - - #[cfg(any(feature = "sandbox", feature = "development", feature = "production"))] + let config_str = if cfg!(feature = "production") { + include_str!("../toml/production.toml") + } else if cfg!(feature = "sandbox") { + include_str!("../toml/sandbox.toml") + } else { + include_str!("../toml/development.toml") + }; + let config = toml::from_str::(config_str); match config { Ok(data) => Ok(data), Err(err) => Err(err.to_string()), @@ -284,6 +292,10 @@ impl ConnectorConfig { AuthenticationConnectors::Threedsecureio => Ok(connector_data.threedsecureio), AuthenticationConnectors::Netcetera => Ok(connector_data.netcetera), AuthenticationConnectors::Gpayments => Ok(connector_data.gpayments), + AuthenticationConnectors::CtpMastercard => Ok(connector_data.ctp_mastercard), + AuthenticationConnectors::UnifiedAuthenticationService => { + Ok(connector_data.unified_authentication_service) + } } } @@ -333,8 +345,10 @@ impl ConnectorConfig { Connector::Bambora => Ok(connector_data.bambora), Connector::Datatrans => Ok(connector_data.datatrans), Connector::Deutschebank => Ok(connector_data.deutschebank), + Connector::Digitalvirgo => Ok(connector_data.digitalvirgo), Connector::Dlocal => Ok(connector_data.dlocal), Connector::Ebanx => Ok(connector_data.ebanx_payout), + Connector::Elavon => Ok(connector_data.elavon), Connector::Fiserv => Ok(connector_data.fiserv), Connector::Fiservemea => Ok(connector_data.fiservemea), Connector::Fiuu => Ok(connector_data.fiuu), @@ -344,11 +358,14 @@ impl ConnectorConfig { Connector::Gocardless => Ok(connector_data.gocardless), Connector::Gpayments => Ok(connector_data.gpayments), Connector::Helcim => Ok(connector_data.helcim), + // Connector::Inespay => Ok(connector_data.inespay), + Connector::Jpmorgan => Ok(connector_data.jpmorgan), Connector::Klarna => Ok(connector_data.klarna), Connector::Mifinity => Ok(connector_data.mifinity), Connector::Mollie => Ok(connector_data.mollie), Connector::Multisafepay => Ok(connector_data.multisafepay), Connector::Nexinets => Ok(connector_data.nexinets), + Connector::Nexixpay => Ok(connector_data.nexixpay), Connector::Prophetpay => Ok(connector_data.prophetpay), Connector::Nmi => Ok(connector_data.nmi), Connector::Novalnet => Ok(connector_data.novalnet), @@ -396,6 +413,7 @@ impl ConnectorConfig { #[cfg(feature = "dummy_connector")] Connector::DummyConnector7 => Ok(connector_data.paypal_test), Connector::Netcetera => Ok(connector_data.netcetera), + Connector::CtpMastercard => Ok(connector_data.ctp_mastercard), } } } diff --git a/crates/connector_configs/src/response_modifier.rs b/crates/connector_configs/src/response_modifier.rs index b6b2e8555e39..8a763b592186 100644 --- a/crates/connector_configs/src/response_modifier.rs +++ b/crates/connector_configs/src/response_modifier.rs @@ -20,6 +20,7 @@ impl ConnectorApiIntegrationPayload { let mut gift_card_details: Vec = Vec::new(); let mut card_redirect_details: Vec = Vec::new(); let mut open_banking_details: Vec = Vec::new(); + let mut mobile_payment_details: Vec = Vec::new(); if let Some(payment_methods_enabled) = response.payment_methods_enabled.clone() { for methods in payment_methods_enabled { @@ -221,6 +222,18 @@ impl ConnectorApiIntegrationPayload { } } } + api_models::enums::PaymentMethod::MobilePayment => { + if let Some(payment_method_types) = methods.payment_method_types { + for method_type in payment_method_types { + mobile_payment_details.push(Provider { + payment_method_type: method_type.payment_method_type, + accepted_currencies: method_type.accepted_currencies.clone(), + accepted_countries: method_type.accepted_countries.clone(), + payment_experience: method_type.payment_experience, + }) + } + } + } } } } diff --git a/crates/connector_configs/src/transformer.rs b/crates/connector_configs/src/transformer.rs index 68ade2909b46..160e938ead3c 100644 --- a/crates/connector_configs/src/transformer.rs +++ b/crates/connector_configs/src/transformer.rs @@ -56,9 +56,15 @@ impl DashboardRequestPayload { | (Connector::Stripe, WeChatPay) => { Some(api_models::enums::PaymentExperience::DisplayQrCode) } - (_, GooglePay) | (_, ApplePay) | (_, PaymentMethodType::SamsungPay) => { + (_, GooglePay) + | (_, ApplePay) + | (_, PaymentMethodType::SamsungPay) + | (_, PaymentMethodType::Paze) => { Some(api_models::enums::PaymentExperience::InvokeSdkClient) } + (_, PaymentMethodType::DirectCarrierBilling) => { + Some(api_models::enums::PaymentExperience::CollectOtp) + } _ => Some(api_models::enums::PaymentExperience::RedirectToUrl), }, } @@ -139,7 +145,8 @@ impl DashboardRequestPayload { | PaymentMethod::Voucher | PaymentMethod::GiftCard | PaymentMethod::OpenBanking - | PaymentMethod::CardRedirect => { + | PaymentMethod::CardRedirect + | PaymentMethod::MobilePayment => { if let Some(provider) = payload.provider { let val = Self::transform_payment_method( request.connector, diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index abe25e5c2b0b..c2d577dce980 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -281,7 +281,7 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] [[adyen.metadata.google_pay]] name="merchant_name" @@ -484,7 +484,7 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] [[authorizedotnet.metadata.google_pay]] name="merchant_name" @@ -813,7 +813,7 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] [[bluesnap.metadata.google_pay]] name="merchant_name" @@ -1115,7 +1115,7 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] [[checkout.metadata.google_pay]] name="merchant_name" @@ -1213,6 +1213,10 @@ merchant_secret="Source verification key" payment_method_type = "apple_pay" [[cybersource.wallet]] payment_method_type = "google_pay" +[[cybersource.wallet]] + payment_method_type = "paze" +[[cybersource.wallet]] + payment_method_type = "samsung_pay" [cybersource.connector_auth.SignatureKey] api_key="Key" key1="Merchant ID" @@ -1294,6 +1298,52 @@ placeholder="Enter Google Pay Merchant Key" required=true type="Text" +[[cybersource.connector_wallets_details.samsung_pay]] +name="service_id" +label="Samsung Pay Service Id" +placeholder="Enter Samsung Pay Service Id" +required=true +type="Text" +[[cybersource.connector_wallets_details.samsung_pay]] +name="merchant_display_name" +label="Display Name" +placeholder="Enter Display Name" +required=true +type="Text" +[[cybersource.connector_wallets_details.samsung_pay]] +name="merchant_business_country" +label="Merchant Business Country" +placeholder="Enter Merchant Business Country" +required=true +type="Select" +options=[] +[[cybersource.connector_wallets_details.samsung_pay]] +name="allowed_brands" +label="Allowed Brands" +placeholder="Enter Allowed Brands" +required=true +type="MultiSelect" +options=["visa","masterCard","amex","discover"] + +[[cybersource.connector_wallets_details.paze]] +name="client_id" +label="Client Id" +placeholder="Enter paze Client Id" +required=true +type="Text" +[[cybersource.connector_wallets_details.paze]] +name="client_profile_id" +label="Client Profile Id" +placeholder="Enter Client Profile Id" +required=true +type="Text" +[[cybersource.connector_wallets_details.paze]] +name="client_name" +label="Display Name" +placeholder="Enter Display Name" +required=true +type="Text" + [cybersource.metadata.acquirer_bin] name="acquirer_bin" label="Acquirer Bin" @@ -1363,6 +1413,13 @@ api_key="Client ID" key1="Merchant ID" api_secret="Client Key" +[digitalvirgo] +[[digitalvirgo.mobile_payment]] + payment_method_type = "direct_carrier_billing" +[digitalvirgo.connector_auth.BodyKey] +api_key="Password" +key1="Username" + [dlocal] [[dlocal.credit]] payment_method_type = "Mastercard" @@ -1674,6 +1731,43 @@ api_key="Client Secret" api_secret="Certificates" key2="Certificate Key" +[jpmorgan] +[[jpmorgan.credit]] + payment_method_type = "AmericanExpress" +[[jpmorgan.credit]] + payment_method_type = "DinersClub" +[[jpmorgan.credit]] + payment_method_type = "Discover" +[[jpmorgan.credit]] + payment_method_type = "JCB" +[[jpmorgan.credit]] + payment_method_type = "Mastercard" +[[jpmorgan.credit]] + payment_method_type = "Discover" +[[jpmorgan.credit]] + payment_method_type = "UnionPay" +[[jpmorgan.credit]] + payment_method_type = "Visa" + [[jpmorgan.debit]] + payment_method_type = "AmericanExpress" +[[jpmorgan.debit]] + payment_method_type = "DinersClub" +[[jpmorgan.debit]] + payment_method_type = "Discover" +[[jpmorgan.debit]] + payment_method_type = "JCB" +[[jpmorgan.debit]] + payment_method_type = "Mastercard" +[[jpmorgan.debit]] + payment_method_type = "Discover" +[[jpmorgan.debit]] + payment_method_type = "UnionPay" +[[jpmorgan.debit]] + payment_method_type = "Visa" +[jpmorgan.connector_auth.BodyKey] +api_key="Client ID" +key1="Client Secret" + [klarna] [[klarna.pay_later]] payment_method_type = "klarna" @@ -1939,7 +2033,27 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] + +[nexixpay] +[[nexixpay.credit]] + payment_method_type = "Mastercard" +[[nexixpay.credit]] + payment_method_type = "Visa" +[[nexixpay.credit]] + payment_method_type = "AmericanExpress" +[[nexixpay.credit]] + payment_method_type = "JCB" +[[nexixpay.debit]] + payment_method_type = "Mastercard" +[[nexixpay.debit]] + payment_method_type = "Visa" +[[nexixpay.debit]] + payment_method_type = "AmericanExpress" +[[nexixpay.debit]] + payment_method_type = "JCB" +[nexixpay.connector_auth.HeaderKey] +api_key="API Key" [nmi] [[nmi.credit]] @@ -2040,7 +2154,7 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] [[nmi.metadata.google_pay]] name="merchant_name" @@ -2180,7 +2294,7 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] [[noon.metadata.google_pay]] name="merchant_name" @@ -2238,10 +2352,89 @@ type="Text" payment_method_type = "CartesBancaires" [[novalnet.debit]] payment_method_type = "UnionPay" +[[novalnet.wallet]] + payment_method_type = "google_pay" +[[novalnet.wallet]] + payment_method_type = "paypal" +[[novalnet.wallet]] + payment_method_type = "apple_pay" [novalnet.connector_auth.SignatureKey] api_key="Product Activation Key" key1="Payment Access Key" api_secret="Tariff ID" +[novalnet.connector_webhook_details] +merchant_secret="Source verification key" +[[novalnet.metadata.google_pay]] +name="merchant_name" +label="Google Pay Merchant Name" +placeholder="Enter Google Pay Merchant Name" +required=true +type="Text" +[[novalnet.metadata.google_pay]] +name="merchant_id" +label="Google Pay Merchant Id" +placeholder="Enter Google Pay Merchant Id" +required=true +type="Text" +[[novalnet.metadata.google_pay]] +name="gateway_merchant_id" +label="Google Pay Merchant Key" +placeholder="Enter Google Pay Merchant Key" +required=true +type="Text" + +[[novalnet.metadata.apple_pay]] +name="certificate" +label="Merchant Certificate (Base64 Encoded)" +placeholder="Enter Merchant Certificate (Base64 Encoded)" +required=true +type="Text" +[[novalnet.metadata.apple_pay]] +name="certificate_keys" +label="Merchant PrivateKey (Base64 Encoded)" +placeholder="Enter Merchant PrivateKey (Base64 Encoded)" +required=true +type="Text" +[[novalnet.metadata.apple_pay]] +name="merchant_identifier" +label="Apple Merchant Identifier" +placeholder="Enter Apple Merchant Identifier" +required=true +type="Text" +[[novalnet.metadata.apple_pay]] +name="display_name" +label="Display Name" +placeholder="Enter Display Name" +required=true +type="Text" +[[novalnet.metadata.apple_pay]] +name="initiative" +label="Domain" +placeholder="Enter Domain" +required=true +type="Select" +options=["web","ios"] +[[novalnet.metadata.apple_pay]] +name="initiative_context" +label="Domain Name" +placeholder="Enter Domain Name" +required=true +type="Text" +[[novalnet.metadata.apple_pay]] +name="merchant_business_country" +label="Merchant Business Country" +placeholder="Enter Merchant Business Country" +required=true +type="Select" +options=[] +[[novalnet.metadata.apple_pay]] +name="payment_processing_details_at" +label="Payment Processing Details At" +placeholder="Enter Payment Processing Details At" +required=true +type="Radio" +options=["Connector"] + [nuvei] [[nuvei.credit]] @@ -2355,7 +2548,7 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] [[nuvei.metadata.google_pay]] name="merchant_name" @@ -2815,7 +3008,7 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] [shift4] [[shift4.credit]] @@ -3220,7 +3413,7 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] [[trustpay.metadata.google_pay]] name="merchant_name" @@ -3386,11 +3579,18 @@ merchant_secret="Source verification key" payment_method_type = "google_pay" [[worldpay.wallet]] payment_method_type = "apple_pay" -[worldpay.connector_auth.BodyKey] -api_key="Username" -key1="Password" +[worldpay.connector_auth.SignatureKey] +key1="Username" +api_key="Password" +api_secret="Merchant Identifier" [worldpay.connector_webhook_details] merchant_secret="Source verification key" +[worldpay.metadata.merchant_name] +name="merchant_name" +label="Name of the merchant to de displayed during 3DS challenge" +placeholder="Enter Name of the merchant" +required=true +type="Text" [[worldpay.metadata.apple_pay]] name="certificate" @@ -3442,7 +3642,7 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] [[worldpay.metadata.google_pay]] name="merchant_name" @@ -4086,9 +4286,189 @@ api_secret="Shared Secret" payment_method_type = "UnionPay" [[fiuu.real_time_payment]] payment_method_type = "duit_now" +[[fiuu.wallet]] + payment_method_type = "google_pay" +[[fiuu.wallet]] + payment_method_type = "apple_pay" [[fiuu.bank_redirect]] payment_method_type = "online_banking_fpx" [fiuu.connector_auth.SignatureKey] api_key="Verify Key" key1="Merchant ID" -api_secret="Secret Key" \ No newline at end of file +api_secret="Secret Key" +[[fiuu.metadata.google_pay]] +name="merchant_name" +label="Google Pay Merchant Name" +placeholder="Enter Google Pay Merchant Name" +required=true +type="Text" +[[fiuu.metadata.google_pay]] +name="merchant_id" +label="Google Pay Merchant Id" +placeholder="Enter Google Pay Merchant Id" +required=true +type="Text" +[[fiuu.metadata.google_pay]] +name="gateway_merchant_id" +label="Google Pay Merchant Key" +placeholder="Enter Google Pay Merchant Key" +required=true +type="Text" + +[[fiuu.metadata.apple_pay]] +name="certificate" +label="Merchant Certificate (Base64 Encoded)" +placeholder="Enter Merchant Certificate (Base64 Encoded)" +required=true +type="Text" +[[fiuu.metadata.apple_pay]] +name="certificate_keys" +label="Merchant PrivateKey (Base64 Encoded)" +placeholder="Enter Merchant PrivateKey (Base64 Encoded)" +required=true +type="Text" +[[fiuu.metadata.apple_pay]] +name="merchant_identifier" +label="Apple Merchant Identifier" +placeholder="Enter Apple Merchant Identifier" +required=true +type="Text" +[[fiuu.metadata.apple_pay]] +name="display_name" +label="Display Name" +placeholder="Enter Display Name" +required=true +type="Text" +[[fiuu.metadata.apple_pay]] +name="initiative" +label="Domain" +placeholder="Enter Domain" +required=true +type="Select" +options=["web","ios"] +[[fiuu.metadata.apple_pay]] +name="initiative_context" +label="Domain Name" +placeholder="Enter Domain Name" +required=true +type="Text" +[[fiuu.metadata.apple_pay]] +name="merchant_business_country" +label="Merchant Business Country" +placeholder="Enter Merchant Business Country" +required=true +type="Select" +options=[] +[[fiuu.metadata.apple_pay]] +name="payment_processing_details_at" +label="Payment Processing Details At" +placeholder="Enter Payment Processing Details At" +required=true +type="Radio" +options=["Hyperswitch"] + +[fiuu.connector_webhook_details] +merchant_secret="Source verification key" + +[elavon] +[[elavon.credit]] + payment_method_type = "Mastercard" +[[elavon.credit]] + payment_method_type = "Visa" +[[elavon.credit]] + payment_method_type = "Interac" +[[elavon.credit]] + payment_method_type = "AmericanExpress" +[[elavon.credit]] + payment_method_type = "JCB" +[[elavon.credit]] + payment_method_type = "DinersClub" +[[elavon.credit]] + payment_method_type = "Discover" +[[elavon.credit]] + payment_method_type = "CartesBancaires" +[[elavon.credit]] + payment_method_type = "UnionPay" +[[elavon.debit]] + payment_method_type = "Mastercard" +[[elavon.debit]] + payment_method_type = "Visa" +[[elavon.debit]] + payment_method_type = "Interac" +[[elavon.debit]] + payment_method_type = "AmericanExpress" +[[elavon.debit]] + payment_method_type = "JCB" +[[elavon.debit]] + payment_method_type = "DinersClub" +[[elavon.debit]] + payment_method_type = "Discover" +[[elavon.debit]] + payment_method_type = "CartesBancaires" +[[elavon.debit]] + payment_method_type = "UnionPay" +[elavon.connector_auth.SignatureKey] +api_key="Account Id" +key1="User ID" +api_secret="Pin" + +[ctp_mastercard] +[ctp_mastercard.connector_auth.HeaderKey] +api_key="API Key" + +[ctp_mastercard.metadata.dpa_id] +name="dpa_id" +label="DPA Id" +placeholder="Enter DPA Id" +required=true +type="Text" + +[ctp_mastercard.metadata.dpa_name] +name="dpa_name" +label="DPA Name" +placeholder="Enter DPA Name" +required=true +type="Text" + +[ctp_mastercard.metadata.locale] +name="locale" +label="Locale" +placeholder="Enter locale" +required=true +type="Text" + +[ctp_mastercard.metadata.card_brands] +name="card_brands" +label="Card Brands" +placeholder="Enter Card Brands" +required=true +type="MultiSelect" +options=["visa","mastercard"] + +[ctp_mastercard.metadata.acquirer_bin] +name="acquirer_bin" +label="Acquire Bin" +placeholder="Enter Acquirer Bin" +required=true +type="Text" + +[ctp_mastercard.metadata.acquirer_merchant_id] +name="acquirer_merchant_id" +label="Acquire Merchant Id" +placeholder="Enter Acquirer Merchant Id" +required=true +type="Text" + +[ctp_mastercard.metadata.merchant_category_code] +name="merchant_category_code" +label="Merchant Category Code" +placeholder="Enter Merchant Category Code" +required=true +type="Text" + +[ctp_mastercard.metadata.merchant_country_code] +name="merchant_country_code" +label="Merchant Country Code" +placeholder="Enter Merchant Country Code" +required=true +type="Text" diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index 32d6d96bb908..aad91f830de4 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -188,7 +188,7 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] [[adyen.metadata.google_pay]] name="merchant_name" @@ -352,7 +352,7 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] [[authorizedotnet.metadata.google_pay]] name="merchant_name" @@ -477,7 +477,7 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] [[bluesnap.metadata.google_pay]] name="merchant_name" @@ -970,7 +970,7 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] [[checkout.metadata.google_pay]] name="merchant_name" @@ -1443,6 +1443,42 @@ api_key="Client Secret" api_secret="Certificates" key2="Certificate Key" +[jpmorgan] +[[jpmorgan.credit]] + payment_method_type = "AmericanExpress" +[[jpmorgan.credit]] + payment_method_type = "DinersClub" +[[jpmorgan.credit]] + payment_method_type = "Discover" +[[jpmorgan.credit]] + payment_method_type = "JCB" +[[jpmorgan.credit]] + payment_method_type = "Mastercard" +[[jpmorgan.credit]] + payment_method_type = "Discover" +[[jpmorgan.credit]] + payment_method_type = "UnionPay" +[[jpmorgan.credit]] + payment_method_type = "Visa" + [[jpmorgan.debit]] + payment_method_type = "AmericanExpress" +[[jpmorgan.debit]] + payment_method_type = "DinersClub" +[[jpmorgan.debit]] + payment_method_type = "Discover" +[[jpmorgan.debit]] + payment_method_type = "JCB" +[[jpmorgan.debit]] + payment_method_type = "Mastercard" +[[jpmorgan.debit]] + payment_method_type = "Discover" +[[jpmorgan.debit]] + payment_method_type = "UnionPay" +[[jpmorgan.debit]] + payment_method_type = "Visa" +[jpmorgan.connector_auth.BodyKey] +api_key="Access Token" + [klarna] [[klarna.pay_later]] payment_method_type = "klarna" @@ -1680,7 +1716,27 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] + +[nexixpay] +[[nexixpay.credit]] + payment_method_type = "Mastercard" +[[nexixpay.credit]] + payment_method_type = "Visa" +[[nexixpay.credit]] + payment_method_type = "AmericanExpress" +[[nexixpay.credit]] + payment_method_type = "JCB" +[[nexixpay.debit]] + payment_method_type = "Mastercard" +[[nexixpay.debit]] + payment_method_type = "Visa" +[[nexixpay.debit]] + payment_method_type = "AmericanExpress" +[[nexixpay.debit]] + payment_method_type = "JCB" +[nexixpay.connector_auth.HeaderKey] +api_key="API Key" [nmi] [[nmi.credit]] @@ -1762,10 +1818,88 @@ merchant_secret="Source verification key" payment_method_type = "CartesBancaires" [[novalnet.debit]] payment_method_type = "UnionPay" +[[novalnet.wallet]] + payment_method_type = "google_pay" +[[novalnet.wallet]] + payment_method_type = "paypal" +[[novalnet.wallet]] + payment_method_type = "apple_pay" [novalnet.connector_auth.SignatureKey] api_key="Product Activation Key" key1="Payment Access Key" api_secret="Tariff ID" +[novalnet.connector_webhook_details] +merchant_secret="Source verification key" +[[novalnet.metadata.google_pay]] +name="merchant_name" +label="Google Pay Merchant Name" +placeholder="Enter Google Pay Merchant Name" +required=true +type="Text" +[[novalnet.metadata.google_pay]] +name="merchant_id" +label="Google Pay Merchant Id" +placeholder="Enter Google Pay Merchant Id" +required=true +type="Text" +[[novalnet.metadata.google_pay]] +name="gateway_merchant_id" +label="Google Pay Merchant Key" +placeholder="Enter Google Pay Merchant Key" +required=true +type="Text" + +[[novalnet.metadata.apple_pay]] +name="certificate" +label="Merchant Certificate (Base64 Encoded)" +placeholder="Enter Merchant Certificate (Base64 Encoded)" +required=true +type="Text" +[[novalnet.metadata.apple_pay]] +name="certificate_keys" +label="Merchant PrivateKey (Base64 Encoded)" +placeholder="Enter Merchant PrivateKey (Base64 Encoded)" +required=true +type="Text" +[[novalnet.metadata.apple_pay]] +name="merchant_identifier" +label="Apple Merchant Identifier" +placeholder="Enter Apple Merchant Identifier" +required=true +type="Text" +[[novalnet.metadata.apple_pay]] +name="display_name" +label="Display Name" +placeholder="Enter Display Name" +required=true +type="Text" +[[novalnet.metadata.apple_pay]] +name="initiative" +label="Domain" +placeholder="Enter Domain" +required=true +type="Select" +options=["web","ios"] +[[novalnet.metadata.apple_pay]] +name="initiative_context" +label="Domain Name" +placeholder="Enter Domain Name" +required=true +type="Text" +[[novalnet.metadata.apple_pay]] +name="merchant_business_country" +label="Merchant Business Country" +placeholder="Enter Merchant Business Country" +required=true +type="Select" +options=[] +[[novalnet.metadata.apple_pay]] +name="payment_processing_details_at" +label="Payment Processing Details At" +placeholder="Enter Payment Processing Details At" +required=true +type="Radio" +options=["Connector"] [nuvei] [[nuvei.credit]] @@ -2032,7 +2166,7 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] [shift4] [[shift4.credit]] @@ -2337,7 +2471,7 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] [[trustpay.metadata.google_pay]] name="merchant_name" @@ -2447,9 +2581,16 @@ merchant_secret="Source verification key" payment_method_type = "google_pay" [[worldpay.wallet]] payment_method_type = "apple_pay" -[worldpay.connector_auth.BodyKey] -api_key="Username" -key1="Password" +[worldpay.connector_auth.SignatureKey] +key1="Username" +api_key="Password" +api_secret="Merchant Identifier" +[worldpay.metadata.merchant_name] +name="merchant_name" +label="Name of the merchant to de displayed during 3DS challenge" +placeholder="Enter Name of the merchant" +required=true +type="Text" [[worldpay.metadata.apple_pay]] name="certificate" @@ -2500,7 +2641,7 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] [[worldpay.metadata.google_pay]] name="merchant_name" @@ -2790,9 +2931,6 @@ merchant_secret="Source verification key" api_key="API Key" [zen.connector_webhook_details] merchant_secret="Source verification key" -[zen.metadata.apple_pay] -terminal_uuid="Terminal UUID" -pay_wall_secret="Pay Wall Secret" [[zen.metadata.apple_pay]] name="terminal_uuid" @@ -2831,11 +2969,6 @@ key1 = "Merchant ID" [netcetera.connector_auth.CertificateAuth] certificate="Base64 encoded PEM formatted certificate chain" private_key="Base64 encoded PEM formatted private key" -[netcetera.metadata] -merchant_country_code="3 digit numeric country code" -merchant_name="Name of the merchant" -three_ds_requestor_name="ThreeDS requestor name" -three_ds_requestor_id="ThreeDS request id" [netcetera.metadata.endpoint_prefix] name="endpoint_prefix" @@ -3083,9 +3216,128 @@ api_secret="Shared Secret" payment_method_type = "UnionPay" [[fiuu.real_time_payment]] payment_method_type = "duit_now" +[[fiuu.wallet]] + payment_method_type = "google_pay" +[[fiuu.wallet]] + payment_method_type = "apple_pay" [[fiuu.bank_redirect]] payment_method_type = "online_banking_fpx" [fiuu.connector_auth.SignatureKey] api_key="Verify Key" key1="Merchant ID" -api_secret="Secret Key" \ No newline at end of file +api_secret="Secret Key" +[[fiuu.metadata.google_pay]] +name="merchant_name" +label="Google Pay Merchant Name" +placeholder="Enter Google Pay Merchant Name" +required=true +type="Text" +[[fiuu.metadata.google_pay]] +name="merchant_id" +label="Google Pay Merchant Id" +placeholder="Enter Google Pay Merchant Id" +required=true +type="Text" +[[fiuu.metadata.google_pay]] +name="gateway_merchant_id" +label="Google Pay Merchant Key" +placeholder="Enter Google Pay Merchant Key" +required=true +type="Text" + +[[fiuu.metadata.apple_pay]] +name="certificate" +label="Merchant Certificate (Base64 Encoded)" +placeholder="Enter Merchant Certificate (Base64 Encoded)" +required=true +type="Text" +[[fiuu.metadata.apple_pay]] +name="certificate_keys" +label="Merchant PrivateKey (Base64 Encoded)" +placeholder="Enter Merchant PrivateKey (Base64 Encoded)" +required=true +type="Text" +[[fiuu.metadata.apple_pay]] +name="merchant_identifier" +label="Apple Merchant Identifier" +placeholder="Enter Apple Merchant Identifier" +required=true +type="Text" +[[fiuu.metadata.apple_pay]] +name="display_name" +label="Display Name" +placeholder="Enter Display Name" +required=true +type="Text" +[[fiuu.metadata.apple_pay]] +name="initiative" +label="Domain" +placeholder="Enter Domain" +required=true +type="Select" +options=["web","ios"] +[[fiuu.metadata.apple_pay]] +name="initiative_context" +label="Domain Name" +placeholder="Enter Domain Name" +required=true +type="Text" +[[fiuu.metadata.apple_pay]] +name="merchant_business_country" +label="Merchant Business Country" +placeholder="Enter Merchant Business Country" +required=true +type="Select" +options=[] +[[fiuu.metadata.apple_pay]] +name="payment_processing_details_at" +label="Payment Processing Details At" +placeholder="Enter Payment Processing Details At" +required=true +type="Radio" +options=["Hyperswitch"] + +[fiuu.connector_webhook_details] +merchant_secret="Source verification key" + +[elavon] +[[elavon.credit]] + payment_method_type = "Mastercard" +[[elavon.credit]] + payment_method_type = "Visa" +[[elavon.credit]] + payment_method_type = "Interac" +[[elavon.credit]] + payment_method_type = "AmericanExpress" +[[elavon.credit]] + payment_method_type = "JCB" +[[elavon.credit]] + payment_method_type = "DinersClub" +[[elavon.credit]] + payment_method_type = "Discover" +[[elavon.credit]] + payment_method_type = "CartesBancaires" +[[elavon.credit]] + payment_method_type = "UnionPay" +[[elavon.debit]] + payment_method_type = "Mastercard" +[[elavon.debit]] + payment_method_type = "Visa" +[[elavon.debit]] + payment_method_type = "Interac" +[[elavon.debit]] + payment_method_type = "AmericanExpress" +[[elavon.debit]] + payment_method_type = "JCB" +[[elavon.debit]] + payment_method_type = "DinersClub" +[[elavon.debit]] + payment_method_type = "Discover" +[[elavon.debit]] + payment_method_type = "CartesBancaires" +[[elavon.debit]] + payment_method_type = "UnionPay" +[elavon.connector_auth.SignatureKey] +api_key="Account Id" +key1="User ID" +api_secret="Pin" \ No newline at end of file diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index b4de938bc803..7126b33b30f5 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -280,7 +280,7 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] [[adyen.metadata.google_pay]] name="merchant_name" @@ -365,8 +365,6 @@ merchant_secret="Source verification key" [airwallex.connector_auth.BodyKey] api_key="API Key" key1="Client ID" -[airwallex.connector_webhook_details] -merchant_secret="Source verification key" [airwallex.connector_webhook_details] merchant_secret="Source verification key" @@ -488,7 +486,7 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] [[authorizedotnet.metadata.google_pay]] name="merchant_name" @@ -814,7 +812,7 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] [[bluesnap.metadata.google_pay]] name="merchant_name" @@ -1115,7 +1113,7 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] [[checkout.metadata.google_pay]] name="merchant_name" @@ -1213,6 +1211,8 @@ merchant_secret="Source verification key" payment_method_type = "apple_pay" [[cybersource.wallet]] payment_method_type = "google_pay" +[[cybersource.wallet]] + payment_method_type = "paze" [cybersource.connector_auth.SignatureKey] api_key="Key" key1="Merchant ID" @@ -1362,6 +1362,13 @@ api_key="Client ID" key1="Merchant ID" api_secret="Client Key" +[digitalvirgo] +[[digitalvirgo.mobile_payment]] + payment_method_type = "direct_carrier_billing" +[digitalvirgo.connector_auth.BodyKey] +api_key="Password" +key1="Username" + [dlocal] [[dlocal.credit]] payment_method_type = "Mastercard" @@ -1672,6 +1679,42 @@ api_key="Client Secret" api_secret="Certificates" key2="Certificate Key" +[jpmorgan] +[[jpmorgan.credit]] + payment_method_type = "AmericanExpress" +[[jpmorgan.credit]] + payment_method_type = "DinersClub" +[[jpmorgan.credit]] + payment_method_type = "Discover" +[[jpmorgan.credit]] + payment_method_type = "JCB" +[[jpmorgan.credit]] + payment_method_type = "Mastercard" +[[jpmorgan.credit]] + payment_method_type = "Discover" +[[jpmorgan.credit]] + payment_method_type = "UnionPay" +[[jpmorgan.credit]] + payment_method_type = "Visa" + [[jpmorgan.debit]] + payment_method_type = "AmericanExpress" +[[jpmorgan.debit]] + payment_method_type = "DinersClub" +[[jpmorgan.debit]] + payment_method_type = "Discover" +[[jpmorgan.debit]] + payment_method_type = "JCB" +[[jpmorgan.debit]] + payment_method_type = "Mastercard" +[[jpmorgan.debit]] + payment_method_type = "Discover" +[[jpmorgan.debit]] + payment_method_type = "UnionPay" +[[jpmorgan.debit]] + payment_method_type = "Visa" +[jpmorgan.connector_auth.BodyKey] +api_key="Access Token" + [klarna] [[klarna.pay_later]] payment_method_type = "klarna" @@ -1937,7 +1980,27 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] + +[nexixpay] +[[nexixpay.credit]] + payment_method_type = "Mastercard" +[[nexixpay.credit]] + payment_method_type = "Visa" +[[nexixpay.credit]] + payment_method_type = "AmericanExpress" +[[nexixpay.credit]] + payment_method_type = "JCB" +[[nexixpay.debit]] + payment_method_type = "Mastercard" +[[nexixpay.debit]] + payment_method_type = "Visa" +[[nexixpay.debit]] + payment_method_type = "AmericanExpress" +[[nexixpay.debit]] + payment_method_type = "JCB" +[nexixpay.connector_auth.HeaderKey] +api_key="API Key" [nmi] [[nmi.credit]] @@ -2037,7 +2100,7 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] [[nmi.metadata.google_pay]] name="merchant_name" @@ -2176,7 +2239,7 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] [[noon.metadata.google_pay]] name="merchant_name" @@ -2234,10 +2297,88 @@ type="Text" payment_method_type = "CartesBancaires" [[novalnet.debit]] payment_method_type = "UnionPay" +[[novalnet.wallet]] + payment_method_type = "google_pay" +[[novalnet.wallet]] + payment_method_type = "paypal" +[[novalnet.wallet]] + payment_method_type = "apple_pay" [novalnet.connector_auth.SignatureKey] api_key="Product Activation Key" key1="Payment Access Key" api_secret="Tariff ID" +[novalnet.connector_webhook_details] +merchant_secret="Source verification key" +[[novalnet.metadata.google_pay]] +name="merchant_name" +label="Google Pay Merchant Name" +placeholder="Enter Google Pay Merchant Name" +required=true +type="Text" +[[novalnet.metadata.google_pay]] +name="merchant_id" +label="Google Pay Merchant Id" +placeholder="Enter Google Pay Merchant Id" +required=true +type="Text" +[[novalnet.metadata.google_pay]] +name="gateway_merchant_id" +label="Google Pay Merchant Key" +placeholder="Enter Google Pay Merchant Key" +required=true +type="Text" + +[[novalnet.metadata.apple_pay]] +name="certificate" +label="Merchant Certificate (Base64 Encoded)" +placeholder="Enter Merchant Certificate (Base64 Encoded)" +required=true +type="Text" +[[novalnet.metadata.apple_pay]] +name="certificate_keys" +label="Merchant PrivateKey (Base64 Encoded)" +placeholder="Enter Merchant PrivateKey (Base64 Encoded)" +required=true +type="Text" +[[novalnet.metadata.apple_pay]] +name="merchant_identifier" +label="Apple Merchant Identifier" +placeholder="Enter Apple Merchant Identifier" +required=true +type="Text" +[[novalnet.metadata.apple_pay]] +name="display_name" +label="Display Name" +placeholder="Enter Display Name" +required=true +type="Text" +[[novalnet.metadata.apple_pay]] +name="initiative" +label="Domain" +placeholder="Enter Domain" +required=true +type="Select" +options=["web","ios"] +[[novalnet.metadata.apple_pay]] +name="initiative_context" +label="Domain Name" +placeholder="Enter Domain Name" +required=true +type="Text" +[[novalnet.metadata.apple_pay]] +name="merchant_business_country" +label="Merchant Business Country" +placeholder="Enter Merchant Business Country" +required=true +type="Select" +options=[] +[[novalnet.metadata.apple_pay]] +name="payment_processing_details_at" +label="Payment Processing Details At" +placeholder="Enter Payment Processing Details At" +required=true +type="Radio" +options=["Connector"] [nuvei] [[nuvei.credit]] @@ -2350,7 +2491,7 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] [[nuvei.metadata.google_pay]] name="merchant_name" @@ -2808,7 +2949,7 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] [shift4] [[shift4.credit]] @@ -3211,7 +3352,7 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] [[trustpay.metadata.google_pay]] name="merchant_name" @@ -3376,11 +3517,18 @@ merchant_secret="Source verification key" payment_method_type = "google_pay" [[worldpay.wallet]] payment_method_type = "apple_pay" -[worldpay.connector_auth.BodyKey] -api_key="Username" -key1="Password" +[worldpay.connector_auth.SignatureKey] +key1="Username" +api_key="Password" +api_secret="Merchant Identifier" [worldpay.connector_webhook_details] merchant_secret="Source verification key" +[worldpay.metadata.merchant_name] +name="merchant_name" +label="Name of the merchant to de displayed during 3DS challenge" +placeholder="Enter Name of the merchant" +required=true +type="Text" [[worldpay.metadata.apple_pay]] name="certificate" @@ -3431,7 +3579,7 @@ label="Payment Processing Details At" placeholder="Enter Payment Processing Details At" required=true type="Radio" -options=["Connector","Hyperswitch"] +options=["Connector"] [[worldpay.metadata.google_pay]] name="merchant_name" @@ -3827,11 +3975,6 @@ type="Toggle" [netcetera.connector_auth.CertificateAuth] certificate="Base64 encoded PEM formatted certificate chain" private_key="Base64 encoded PEM formatted private key" -[netcetera.metadata] -merchant_country_code="3 digit numeric country code" -merchant_name="Name of the merchant" -three_ds_requestor_name="ThreeDS requestor name" -three_ds_requestor_id="ThreeDS request id" [netcetera.metadata.endpoint_prefix] name="endpoint_prefix" @@ -4080,9 +4223,189 @@ api_secret="Shared Secret" payment_method_type = "UnionPay" [[fiuu.real_time_payment]] payment_method_type = "duit_now" +[[fiuu.wallet]] + payment_method_type = "google_pay" +[[fiuu.wallet]] + payment_method_type = "apple_pay" [[fiuu.bank_redirect]] payment_method_type = "online_banking_fpx" [fiuu.connector_auth.SignatureKey] api_key="Verify Key" key1="Merchant ID" -api_secret="Secret Key" \ No newline at end of file +api_secret="Secret Key" +[[fiuu.metadata.google_pay]] +name="merchant_name" +label="Google Pay Merchant Name" +placeholder="Enter Google Pay Merchant Name" +required=true +type="Text" +[[fiuu.metadata.google_pay]] +name="merchant_id" +label="Google Pay Merchant Id" +placeholder="Enter Google Pay Merchant Id" +required=true +type="Text" +[[fiuu.metadata.google_pay]] +name="gateway_merchant_id" +label="Google Pay Merchant Key" +placeholder="Enter Google Pay Merchant Key" +required=true +type="Text" + +[[fiuu.metadata.apple_pay]] +name="certificate" +label="Merchant Certificate (Base64 Encoded)" +placeholder="Enter Merchant Certificate (Base64 Encoded)" +required=true +type="Text" +[[fiuu.metadata.apple_pay]] +name="certificate_keys" +label="Merchant PrivateKey (Base64 Encoded)" +placeholder="Enter Merchant PrivateKey (Base64 Encoded)" +required=true +type="Text" +[[fiuu.metadata.apple_pay]] +name="merchant_identifier" +label="Apple Merchant Identifier" +placeholder="Enter Apple Merchant Identifier" +required=true +type="Text" +[[fiuu.metadata.apple_pay]] +name="display_name" +label="Display Name" +placeholder="Enter Display Name" +required=true +type="Text" +[[fiuu.metadata.apple_pay]] +name="initiative" +label="Domain" +placeholder="Enter Domain" +required=true +type="Select" +options=["web","ios"] +[[fiuu.metadata.apple_pay]] +name="initiative_context" +label="Domain Name" +placeholder="Enter Domain Name" +required=true +type="Text" +[[fiuu.metadata.apple_pay]] +name="merchant_business_country" +label="Merchant Business Country" +placeholder="Enter Merchant Business Country" +required=true +type="Select" +options=[] +[[fiuu.metadata.apple_pay]] +name="payment_processing_details_at" +label="Payment Processing Details At" +placeholder="Enter Payment Processing Details At" +required=true +type="Radio" +options=["Hyperswitch"] + +[fiuu.connector_webhook_details] +merchant_secret="Source verification key" + +[elavon] +[[elavon.credit]] + payment_method_type = "Mastercard" +[[elavon.credit]] + payment_method_type = "Visa" +[[elavon.credit]] + payment_method_type = "Interac" +[[elavon.credit]] + payment_method_type = "AmericanExpress" +[[elavon.credit]] + payment_method_type = "JCB" +[[elavon.credit]] + payment_method_type = "DinersClub" +[[elavon.credit]] + payment_method_type = "Discover" +[[elavon.credit]] + payment_method_type = "CartesBancaires" +[[elavon.credit]] + payment_method_type = "UnionPay" +[[elavon.debit]] + payment_method_type = "Mastercard" +[[elavon.debit]] + payment_method_type = "Visa" +[[elavon.debit]] + payment_method_type = "Interac" +[[elavon.debit]] + payment_method_type = "AmericanExpress" +[[elavon.debit]] + payment_method_type = "JCB" +[[elavon.debit]] + payment_method_type = "DinersClub" +[[elavon.debit]] + payment_method_type = "Discover" +[[elavon.debit]] + payment_method_type = "CartesBancaires" +[[elavon.debit]] + payment_method_type = "UnionPay" +[elavon.connector_auth.SignatureKey] +api_key="Account Id" +key1="User ID" +api_secret="Pin" + +[ctp_mastercard] +[ctp_mastercard.connector_auth.HeaderKey] +api_key="API Key" + +[ctp_mastercard.metadata.dpa_id] +name="dpa_id" +label="DPA Id" +placeholder="Enter DPA Id" +required=true +type="Text" + +[ctp_mastercard.metadata.dpa_name] +name="dpa_name" +label="DPA Name" +placeholder="Enter DPA Name" +required=true +type="Text" + +[ctp_mastercard.metadata.locale] +name="locale" +label="Locale" +placeholder="Enter locale" +required=true +type="Text" + +[[ctp_mastercard.metadata.card_brands]] +name="card_brands" +label="Card Brands" +placeholder="Enter Card Brands" +required=true +type="MultiSelect" +options=["visa","mastercard"] + +[[ctp_mastercard.metadata.acquirer_bin]] +name="acquirer_bin" +label="Acquire Bin" +placeholder="Enter Acquirer Bin" +required=true +type="Text" + +[[ctp_mastercard.metadata.acquirer_merchant_id]] +name="acquirer_merchant_id" +label="Acquire Merchant Id" +placeholder="Enter Acquirer Merchant Id" +required=true +type="Text" + +[[ctp_mastercard.metadata.merchant_category_code]] +name="merchant_category_code" +label="Merchant Category Code" +placeholder="Enter Merchant Category Code" +required=true +type="Text" + +[[ctp_mastercard.metadata.merchant_country_code]] +name="merchant_country_code" +label="Merchant Country Code" +placeholder="Enter Merchant Country Code" +required=true +type="Text" diff --git a/crates/currency_conversion/src/types.rs b/crates/currency_conversion/src/types.rs index a84520dca0ad..2001495b2dba 100644 --- a/crates/currency_conversion/src/types.rs +++ b/crates/currency_conversion/src/types.rs @@ -78,6 +78,7 @@ impl ExchangeRates { pub fn currency_match(currency: Currency) -> &'static iso::Currency { match currency { Currency::AED => iso::AED, + Currency::AFN => iso::AFN, Currency::ALL => iso::ALL, Currency::AMD => iso::AMD, Currency::ANG => iso::ANG, @@ -97,10 +98,12 @@ pub fn currency_match(currency: Currency) -> &'static iso::Currency { Currency::BOB => iso::BOB, Currency::BRL => iso::BRL, Currency::BSD => iso::BSD, + Currency::BTN => iso::BTN, Currency::BWP => iso::BWP, Currency::BYN => iso::BYN, Currency::BZD => iso::BZD, Currency::CAD => iso::CAD, + Currency::CDF => iso::CDF, Currency::CHF => iso::CHF, Currency::CLP => iso::CLP, Currency::CNY => iso::CNY, @@ -114,6 +117,7 @@ pub fn currency_match(currency: Currency) -> &'static iso::Currency { Currency::DOP => iso::DOP, Currency::DZD => iso::DZD, Currency::EGP => iso::EGP, + Currency::ERN => iso::ERN, Currency::ETB => iso::ETB, Currency::EUR => iso::EUR, Currency::FJD => iso::FJD, @@ -135,6 +139,8 @@ pub fn currency_match(currency: Currency) -> &'static iso::Currency { Currency::ILS => iso::ILS, Currency::INR => iso::INR, Currency::IQD => iso::IQD, + Currency::IRR => iso::IRR, + Currency::ISK => iso::ISK, Currency::JMD => iso::JMD, Currency::JOD => iso::JOD, Currency::JPY => iso::JPY, @@ -142,6 +148,7 @@ pub fn currency_match(currency: Currency) -> &'static iso::Currency { Currency::KGS => iso::KGS, Currency::KHR => iso::KHR, Currency::KMF => iso::KMF, + Currency::KPW => iso::KPW, Currency::KRW => iso::KRW, Currency::KWD => iso::KWD, Currency::KYD => iso::KYD, @@ -188,6 +195,7 @@ pub fn currency_match(currency: Currency) -> &'static iso::Currency { Currency::SAR => iso::SAR, Currency::SBD => iso::SBD, Currency::SCR => iso::SCR, + Currency::SDG => iso::SDG, Currency::SEK => iso::SEK, Currency::SGD => iso::SGD, Currency::SHP => iso::SHP, @@ -198,9 +206,12 @@ pub fn currency_match(currency: Currency) -> &'static iso::Currency { Currency::SSP => iso::SSP, Currency::STN => iso::STN, Currency::SVC => iso::SVC, + Currency::SYP => iso::SYP, Currency::SZL => iso::SZL, Currency::THB => iso::THB, + Currency::TJS => iso::TJS, Currency::TND => iso::TND, + Currency::TMT => iso::TMT, Currency::TOP => iso::TOP, Currency::TTD => iso::TTD, Currency::TRY => iso::TRY, @@ -222,5 +233,6 @@ pub fn currency_match(currency: Currency) -> &'static iso::Currency { Currency::YER => iso::YER, Currency::ZAR => iso::ZAR, Currency::ZMW => iso::ZMW, + Currency::ZWL => iso::ZWL, } } diff --git a/crates/diesel_models/Cargo.toml b/crates/diesel_models/Cargo.toml index 02d917f3b17b..5da67eb30759 100644 --- a/crates/diesel_models/Cargo.toml +++ b/crates/diesel_models/Cargo.toml @@ -10,8 +10,8 @@ license.workspace = true [features] default = ["kv_store"] kv_store = [] -v1 = [] -v2 = [] +v1 = ["common_utils/v1"] +v2 = ["common_utils/v2"] customer_v2 = [] payment_methods_v2 = [] @@ -30,6 +30,7 @@ time = { version = "0.3.35", features = ["serde", "serde-well-known", "std"] } # First party crates common_enums = { version = "0.1.0", path = "../common_enums" } common_utils = { version = "0.1.0", path = "../common_utils" } +common_types = { version = "0.1.0", path = "../common_types" } masking = { version = "0.1.0", path = "../masking" } router_derive = { version = "0.1.0", path = "../router_derive" } router_env = { version = "0.1.0", path = "../router_env", features = ["log_extra_implicit_fields", "log_custom_entries_to_extra"] } diff --git a/crates/diesel_models/src/address.rs b/crates/diesel_models/src/address.rs index a1cfb668716b..06b82cb2c204 100644 --- a/crates/diesel_models/src/address.rs +++ b/crates/diesel_models/src/address.rs @@ -1,12 +1,5 @@ -use common_utils::{ - crypto::{self, Encryptable}, - encryption::Encryption, - pii::EmailStrategy, - types::keymanager::ToEncryptable, -}; +use common_utils::{crypto, encryption::Encryption}; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; -use masking::{Secret, SwitchStrategy}; -use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -74,48 +67,6 @@ pub struct EncryptableAddress { pub email: crypto::OptionalEncryptableEmail, } -impl ToEncryptable, Encryption> for Address { - fn to_encryptable(self) -> FxHashMap { - let mut map = FxHashMap::with_capacity_and_hasher(9, Default::default()); - self.line1.map(|x| map.insert("line1".to_string(), x)); - self.line2.map(|x| map.insert("line2".to_string(), x)); - self.line3.map(|x| map.insert("line3".to_string(), x)); - self.zip.map(|x| map.insert("zip".to_string(), x)); - self.state.map(|x| map.insert("state".to_string(), x)); - self.first_name - .map(|x| map.insert("first_name".to_string(), x)); - self.last_name - .map(|x| map.insert("last_name".to_string(), x)); - self.phone_number - .map(|x| map.insert("phone_number".to_string(), x)); - self.email.map(|x| map.insert("email".to_string(), x)); - map - } - - fn from_encryptable( - mut hashmap: FxHashMap>>, - ) -> common_utils::errors::CustomResult - { - Ok(EncryptableAddress { - line1: hashmap.remove("line1"), - line2: hashmap.remove("line2"), - line3: hashmap.remove("line3"), - zip: hashmap.remove("zip"), - state: hashmap.remove("state"), - first_name: hashmap.remove("first_name"), - last_name: hashmap.remove("last_name"), - phone_number: hashmap.remove("phone_number"), - email: hashmap.remove("email").map(|email| { - let encryptable: Encryptable> = Encryptable::new( - email.clone().into_inner().switch_strategy(), - email.into_encrypted(), - ); - encryptable - }), - }) - } -} - #[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay, Serialize, Deserialize)] #[diesel(table_name = address)] pub struct AddressUpdateInternal { diff --git a/crates/diesel_models/src/api_keys.rs b/crates/diesel_models/src/api_keys.rs index 1781e65cded4..7076bf597e0e 100644 --- a/crates/diesel_models/src/api_keys.rs +++ b/crates/diesel_models/src/api_keys.rs @@ -9,7 +9,7 @@ use crate::schema::api_keys; )] #[diesel(table_name = api_keys, primary_key(key_id), check_for_backend(diesel::pg::Pg))] pub struct ApiKey { - pub key_id: String, + pub key_id: common_utils::id_type::ApiKeyId, pub merchant_id: common_utils::id_type::MerchantId, pub name: String, pub description: Option, @@ -23,7 +23,7 @@ pub struct ApiKey { #[derive(Debug, Insertable)] #[diesel(table_name = api_keys)] pub struct ApiKeyNew { - pub key_id: String, + pub key_id: common_utils::id_type::ApiKeyId, pub merchant_id: common_utils::id_type::MerchantId, pub name: String, pub description: Option, @@ -141,7 +141,7 @@ mod diesel_impl { // Tracking data by process_tracker #[derive(Default, Debug, Deserialize, Serialize, Clone)] pub struct ApiKeyExpiryTrackingData { - pub key_id: String, + pub key_id: common_utils::id_type::ApiKeyId, pub merchant_id: common_utils::id_type::MerchantId, pub api_key_name: String, pub prefix: String, diff --git a/crates/diesel_models/src/authentication.rs b/crates/diesel_models/src/authentication.rs index 0310e60ebbd8..c79892d27bf6 100644 --- a/crates/diesel_models/src/authentication.rs +++ b/crates/diesel_models/src/authentication.rs @@ -47,6 +47,7 @@ pub struct Authentication { pub ds_trans_id: Option, pub directory_server_id: Option, pub acquirer_country_code: Option, + pub service_details: Option, } impl Authentication { @@ -94,6 +95,7 @@ pub struct AuthenticationNew { pub ds_trans_id: Option, pub directory_server_id: Option, pub acquirer_country_code: Option, + pub service_details: Option, } #[derive(Debug)] @@ -152,6 +154,10 @@ pub enum AuthenticationUpdate { PostAuthorizationUpdate { authentication_lifecycle_status: common_enums::AuthenticationLifecycleStatus, }, + AuthenticationStatusUpdate { + trans_status: common_enums::TransactionStatus, + authentication_status: common_enums::AuthenticationStatus, + }, } #[derive(Clone, Debug, Eq, PartialEq, AsChangeset, Serialize, Deserialize)] @@ -186,6 +192,7 @@ pub struct AuthenticationUpdateInternal { pub ds_trans_id: Option, pub directory_server_id: Option, pub acquirer_country_code: Option, + pub service_details: Option, } impl Default for AuthenticationUpdateInternal { @@ -219,6 +226,7 @@ impl Default for AuthenticationUpdateInternal { ds_trans_id: Default::default(), directory_server_id: Default::default(), acquirer_country_code: Default::default(), + service_details: Default::default(), } } } @@ -254,6 +262,7 @@ impl AuthenticationUpdateInternal { ds_trans_id, directory_server_id, acquirer_country_code, + service_details, } = self; Authentication { connector_authentication_id: connector_authentication_id @@ -288,6 +297,7 @@ impl AuthenticationUpdateInternal { ds_trans_id: ds_trans_id.or(source.ds_trans_id), directory_server_id: directory_server_id.or(source.directory_server_id), acquirer_country_code: acquirer_country_code.or(source.acquirer_country_code), + service_details: service_details.or(source.service_details), ..source } } @@ -418,6 +428,14 @@ impl From for AuthenticationUpdateInternal { connector_metadata, ..Default::default() }, + AuthenticationUpdate::AuthenticationStatusUpdate { + trans_status, + authentication_status, + } => Self { + trans_status: Some(trans_status), + authentication_status: Some(authentication_status), + ..Default::default() + }, } } } diff --git a/crates/diesel_models/src/business_profile.rs b/crates/diesel_models/src/business_profile.rs index fd0abc16614d..dff3f174fc50 100644 --- a/crates/diesel_models/src/business_profile.rs +++ b/crates/diesel_models/src/business_profile.rs @@ -57,6 +57,8 @@ pub struct Profile { pub is_network_tokenization_enabled: bool, pub is_auto_retries_enabled: Option, pub max_auto_retries_enabled: Option, + pub is_click_to_pay_enabled: bool, + pub authentication_product_ids: Option, } #[cfg(feature = "v1")] @@ -100,6 +102,8 @@ pub struct ProfileNew { pub is_network_tokenization_enabled: bool, pub is_auto_retries_enabled: Option, pub max_auto_retries_enabled: Option, + pub is_click_to_pay_enabled: bool, + pub authentication_product_ids: Option, } #[cfg(feature = "v1")] @@ -140,6 +144,8 @@ pub struct ProfileUpdateInternal { pub is_network_tokenization_enabled: Option, pub is_auto_retries_enabled: Option, pub max_auto_retries_enabled: Option, + pub is_click_to_pay_enabled: Option, + pub authentication_product_ids: Option, } #[cfg(feature = "v1")] @@ -179,6 +185,8 @@ impl ProfileUpdateInternal { is_network_tokenization_enabled, is_auto_retries_enabled, max_auto_retries_enabled, + is_click_to_pay_enabled, + authentication_product_ids, } = self; Profile { profile_id: source.profile_id, @@ -238,6 +246,10 @@ impl ProfileUpdateInternal { .unwrap_or(source.is_network_tokenization_enabled), is_auto_retries_enabled: is_auto_retries_enabled.or(source.is_auto_retries_enabled), max_auto_retries_enabled: max_auto_retries_enabled.or(source.max_auto_retries_enabled), + is_click_to_pay_enabled: is_click_to_pay_enabled + .unwrap_or(source.is_click_to_pay_enabled), + authentication_product_ids: authentication_product_ids + .or(source.authentication_product_ids), } } } @@ -255,7 +267,7 @@ pub struct Profile { pub profile_name: String, pub created_at: time::PrimitiveDateTime, pub modified_at: time::PrimitiveDateTime, - pub return_url: Option, + pub return_url: Option, pub enable_payment_response_hash: bool, pub payment_response_hash_key: Option, pub redirect_to_merchant_with_http_post: bool, @@ -285,12 +297,15 @@ pub struct Profile { pub frm_routing_algorithm_id: Option, pub payout_routing_algorithm_id: Option, pub default_fallback_routing: Option, + pub should_collect_cvv_during_payment: bool, pub id: common_utils::id_type::ProfileId, pub version: common_enums::ApiVersion, pub dynamic_routing_algorithm: Option, pub is_network_tokenization_enabled: bool, pub is_auto_retries_enabled: Option, pub max_auto_retries_enabled: Option, + pub is_click_to_pay_enabled: bool, + pub authentication_product_ids: Option, } impl Profile { @@ -313,7 +328,7 @@ pub struct ProfileNew { pub profile_name: String, pub created_at: time::PrimitiveDateTime, pub modified_at: time::PrimitiveDateTime, - pub return_url: Option, + pub return_url: Option, pub enable_payment_response_hash: bool, pub payment_response_hash_key: Option, pub redirect_to_merchant_with_http_post: bool, @@ -343,11 +358,14 @@ pub struct ProfileNew { pub frm_routing_algorithm_id: Option, pub payout_routing_algorithm_id: Option, pub default_fallback_routing: Option, + pub should_collect_cvv_during_payment: bool, pub id: common_utils::id_type::ProfileId, pub version: common_enums::ApiVersion, pub is_network_tokenization_enabled: bool, pub is_auto_retries_enabled: Option, pub max_auto_retries_enabled: Option, + pub is_click_to_pay_enabled: bool, + pub authentication_product_ids: Option, } #[cfg(feature = "v2")] @@ -356,7 +374,7 @@ pub struct ProfileNew { pub struct ProfileUpdateInternal { pub profile_name: Option, pub modified_at: time::PrimitiveDateTime, - pub return_url: Option, + pub return_url: Option, pub enable_payment_response_hash: Option, pub payment_response_hash_key: Option, pub redirect_to_merchant_with_http_post: Option, @@ -386,9 +404,12 @@ pub struct ProfileUpdateInternal { pub frm_routing_algorithm_id: Option, pub payout_routing_algorithm_id: Option, pub default_fallback_routing: Option, + pub should_collect_cvv_during_payment: Option, pub is_network_tokenization_enabled: Option, pub is_auto_retries_enabled: Option, pub max_auto_retries_enabled: Option, + pub is_click_to_pay_enabled: Option, + pub authentication_product_ids: Option, } #[cfg(feature = "v2")] @@ -426,9 +447,12 @@ impl ProfileUpdateInternal { frm_routing_algorithm_id, payout_routing_algorithm_id, default_fallback_routing, + should_collect_cvv_during_payment, is_network_tokenization_enabled, is_auto_retries_enabled, max_auto_retries_enabled, + is_click_to_pay_enabled, + authentication_product_ids, } = self; Profile { id: source.id, @@ -485,12 +509,18 @@ impl ProfileUpdateInternal { payout_routing_algorithm_id: payout_routing_algorithm_id .or(source.payout_routing_algorithm_id), default_fallback_routing: default_fallback_routing.or(source.default_fallback_routing), + should_collect_cvv_during_payment: should_collect_cvv_during_payment + .unwrap_or(source.should_collect_cvv_during_payment), version: source.version, dynamic_routing_algorithm: None, is_network_tokenization_enabled: is_network_tokenization_enabled .unwrap_or(source.is_network_tokenization_enabled), is_auto_retries_enabled: is_auto_retries_enabled.or(source.is_auto_retries_enabled), max_auto_retries_enabled: max_auto_retries_enabled.or(source.max_auto_retries_enabled), + is_click_to_pay_enabled: is_click_to_pay_enabled + .unwrap_or(source.is_click_to_pay_enabled), + authentication_product_ids: authentication_product_ids + .or(source.authentication_product_ids), } } } @@ -526,6 +556,7 @@ pub struct BusinessPaymentLinkConfig { pub default_config: Option, pub business_specific_configs: Option>, pub allowed_domains: Option>, + pub branding_visibility: Option, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] @@ -536,6 +567,18 @@ pub struct PaymentLinkConfigRequest { pub sdk_layout: Option, pub display_sdk_only: Option, pub enabled_saved_payment_method: Option, + pub hide_card_nickname_field: Option, + pub show_card_form_by_default: Option, + pub background_image: Option, + pub details_layout: Option, + pub payment_button_text: Option, +} + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq)] +pub struct PaymentLinkBackgroundImageConfig { + pub url: common_utils::types::Url, + pub position: Option, + pub size: Option, } common_utils::impl_to_sql_from_sql_json!(BusinessPaymentLinkConfig); diff --git a/crates/diesel_models/src/capture.rs b/crates/diesel_models/src/capture.rs index 0b0b86222e21..d6272c34060b 100644 --- a/crates/diesel_models/src/capture.rs +++ b/crates/diesel_models/src/capture.rs @@ -1,4 +1,4 @@ -use common_utils::types::MinorUnit; +use common_utils::types::{ConnectorTransactionId, MinorUnit}; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -6,7 +6,7 @@ use time::PrimitiveDateTime; use crate::{enums as storage_enums, schema::captures}; #[derive( - Clone, Debug, Eq, PartialEq, Identifiable, Queryable, Selectable, Serialize, Deserialize, Hash, + Clone, Debug, Eq, PartialEq, Identifiable, Queryable, Selectable, Serialize, Deserialize, )] #[diesel(table_name = captures, primary_key(capture_id), check_for_backend(diesel::pg::Pg))] pub struct Capture { @@ -26,10 +26,11 @@ pub struct Capture { #[serde(with = "common_utils::custom_serde::iso8601")] pub modified_at: PrimitiveDateTime, pub authorized_attempt_id: String, - pub connector_capture_id: Option, + pub connector_capture_id: Option, pub capture_sequence: i16, // reference to the capture at connector side pub connector_response_reference_id: Option, + pub connector_capture_data: Option, } #[derive(Clone, Debug, Insertable, router_derive::DebugAsDisplay, Serialize, Deserialize)] @@ -51,17 +52,19 @@ pub struct CaptureNew { #[serde(with = "common_utils::custom_serde::iso8601")] pub modified_at: PrimitiveDateTime, pub authorized_attempt_id: String, - pub connector_capture_id: Option, + pub connector_capture_id: Option, pub capture_sequence: i16, pub connector_response_reference_id: Option, + pub connector_capture_data: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub enum CaptureUpdate { ResponseUpdate { status: storage_enums::CaptureStatus, - connector_capture_id: Option, + connector_capture_id: Option, connector_response_reference_id: Option, + connector_capture_data: Option, }, ErrorUpdate { status: storage_enums::CaptureStatus, @@ -79,8 +82,9 @@ pub struct CaptureUpdateInternal { pub error_code: Option, pub error_reason: Option, pub modified_at: Option, - pub connector_capture_id: Option, + pub connector_capture_id: Option, pub connector_response_reference_id: Option, + pub connector_capture_data: Option, } impl CaptureUpdate { @@ -93,6 +97,7 @@ impl CaptureUpdate { modified_at: _, connector_capture_id, connector_response_reference_id, + connector_capture_data, } = self.into(); Capture { status: status.unwrap_or(source.status), @@ -103,6 +108,7 @@ impl CaptureUpdate { connector_capture_id: connector_capture_id.or(source.connector_capture_id), connector_response_reference_id: connector_response_reference_id .or(source.connector_response_reference_id), + connector_capture_data: connector_capture_data.or(source.connector_capture_data), ..source } } @@ -116,11 +122,13 @@ impl From for CaptureUpdateInternal { status, connector_capture_id: connector_transaction_id, connector_response_reference_id, + connector_capture_data, } => Self { status: Some(status), connector_capture_id: connector_transaction_id, modified_at: now, connector_response_reference_id, + connector_capture_data, ..Self::default() }, CaptureUpdate::ErrorUpdate { diff --git a/crates/diesel_models/src/configs.rs b/crates/diesel_models/src/configs.rs index 2b30aa6a9724..37381961d967 100644 --- a/crates/diesel_models/src/configs.rs +++ b/crates/diesel_models/src/configs.rs @@ -7,7 +7,6 @@ use crate::schema::configs; #[derive(Default, Clone, Debug, Insertable, Serialize, Deserialize)] #[diesel(table_name = configs)] - pub struct ConfigNew { pub key: String, pub config: String, diff --git a/crates/diesel_models/src/customers.rs b/crates/diesel_models/src/customers.rs index 46b5059b42a2..7bd7d8368a71 100644 --- a/crates/diesel_models/src/customers.rs +++ b/crates/diesel_models/src/customers.rs @@ -91,7 +91,7 @@ pub struct CustomerNew { pub default_billing_address: Option, pub default_shipping_address: Option, pub status: DeleteStatus, - pub id: String, + pub id: common_utils::id_type::GlobalCustomerId, } #[cfg(all(feature = "v2", feature = "customer_v2"))] @@ -173,7 +173,7 @@ pub struct Customer { pub default_billing_address: Option, pub default_shipping_address: Option, pub status: DeleteStatus, - pub id: String, + pub id: common_utils::id_type::GlobalCustomerId, } #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] diff --git a/crates/diesel_models/src/dispute.rs b/crates/diesel_models/src/dispute.rs index 130e46aa9cc1..8e3bab20d89c 100644 --- a/crates/diesel_models/src/dispute.rs +++ b/crates/diesel_models/src/dispute.rs @@ -31,6 +31,7 @@ pub struct DisputeNew { pub merchant_connector_id: Option, pub dispute_amount: i64, pub organization_id: common_utils::id_type::OrganizationId, + pub dispute_currency: Option, } #[derive(Clone, Debug, PartialEq, Serialize, Identifiable, Queryable, Selectable)] @@ -61,6 +62,7 @@ pub struct Dispute { pub merchant_connector_id: Option, pub dispute_amount: i64, pub organization_id: common_utils::id_type::OrganizationId, + pub dispute_currency: Option, } #[derive(Debug)] diff --git a/crates/diesel_models/src/dynamic_routing_stats.rs b/crates/diesel_models/src/dynamic_routing_stats.rs new file mode 100644 index 000000000000..c055359d8b03 --- /dev/null +++ b/crates/diesel_models/src/dynamic_routing_stats.rs @@ -0,0 +1,43 @@ +use diesel::{Insertable, Queryable, Selectable}; + +use crate::schema::dynamic_routing_stats; + +#[derive(Clone, Debug, Eq, Insertable, PartialEq)] +#[diesel(table_name = dynamic_routing_stats)] +pub struct DynamicRoutingStatsNew { + pub payment_id: common_utils::id_type::PaymentId, + pub attempt_id: String, + pub merchant_id: common_utils::id_type::MerchantId, + pub profile_id: common_utils::id_type::ProfileId, + pub amount: common_utils::types::MinorUnit, + pub success_based_routing_connector: String, + pub payment_connector: String, + pub currency: Option, + pub payment_method: Option, + pub capture_method: Option, + pub authentication_type: Option, + pub payment_status: common_enums::AttemptStatus, + pub conclusive_classification: common_enums::SuccessBasedRoutingConclusiveState, + pub created_at: time::PrimitiveDateTime, + pub payment_method_type: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, Queryable, Selectable, Insertable)] +#[diesel(table_name = dynamic_routing_stats, primary_key(payment_id), check_for_backend(diesel::pg::Pg))] +pub struct DynamicRoutingStats { + pub payment_id: common_utils::id_type::PaymentId, + pub attempt_id: String, + pub merchant_id: common_utils::id_type::MerchantId, + pub profile_id: common_utils::id_type::ProfileId, + pub amount: common_utils::types::MinorUnit, + pub success_based_routing_connector: String, + pub payment_connector: String, + pub currency: Option, + pub payment_method: Option, + pub capture_method: Option, + pub authentication_type: Option, + pub payment_status: common_enums::AttemptStatus, + pub conclusive_classification: common_enums::SuccessBasedRoutingConclusiveState, + pub created_at: time::PrimitiveDateTime, + pub payment_method_type: Option, +} diff --git a/crates/diesel_models/src/enums.rs b/crates/diesel_models/src/enums.rs index 77d167402ef0..ec6e91a2ecb0 100644 --- a/crates/diesel_models/src/enums.rs +++ b/crates/diesel_models/src/enums.rs @@ -17,9 +17,12 @@ pub mod diesel_exports { DbPaymentMethodIssuerCode as PaymentMethodIssuerCode, DbPaymentSource as PaymentSource, DbPaymentType as PaymentType, DbPayoutStatus as PayoutStatus, DbPayoutType as PayoutType, DbProcessTrackerStatus as ProcessTrackerStatus, DbReconStatus as ReconStatus, - DbRefundStatus as RefundStatus, DbRefundType as RefundType, + DbRefundStatus as RefundStatus, DbRefundType as RefundType, DbRelayStatus as RelayStatus, + DbRelayType as RelayType, DbRequestIncrementalAuthorization as RequestIncrementalAuthorization, DbRoleScope as RoleScope, DbRoutingAlgorithmKind as RoutingAlgorithmKind, + DbScaExemptionType as ScaExemptionType, + DbSuccessBasedRoutingConclusiveState as SuccessBasedRoutingConclusiveState, DbTotpStatus as TotpStatus, DbTransactionType as TransactionType, DbUserRoleVersion as UserRoleVersion, DbUserStatus as UserStatus, DbWebhookDeliveryAttempt as WebhookDeliveryAttempt, diff --git a/crates/diesel_models/src/ephemeral_key.rs b/crates/diesel_models/src/ephemeral_key.rs index d398ecdf784a..c7fc103ed09b 100644 --- a/crates/diesel_models/src/ephemeral_key.rs +++ b/crates/diesel_models/src/ephemeral_key.rs @@ -1,3 +1,33 @@ +#[cfg(feature = "v2")] +use masking::{PeekInterface, Secret}; +#[cfg(feature = "v2")] +pub struct EphemeralKeyTypeNew { + pub id: common_utils::id_type::EphemeralKeyId, + pub merchant_id: common_utils::id_type::MerchantId, + pub customer_id: common_utils::id_type::GlobalCustomerId, + pub secret: Secret, + pub resource_type: ResourceType, +} + +#[cfg(feature = "v2")] +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct EphemeralKeyType { + pub id: common_utils::id_type::EphemeralKeyId, + pub merchant_id: common_utils::id_type::MerchantId, + pub customer_id: common_utils::id_type::GlobalCustomerId, + pub resource_type: ResourceType, + pub created_at: time::PrimitiveDateTime, + pub expires: time::PrimitiveDateTime, + pub secret: Secret, +} + +#[cfg(feature = "v2")] +impl EphemeralKeyType { + pub fn generate_secret_key(&self) -> String { + format!("epkey_{}", self.secret.peek()) + } +} + pub struct EphemeralKeyNew { pub id: String, pub merchant_id: common_utils::id_type::MerchantId, @@ -20,3 +50,21 @@ impl common_utils::events::ApiEventMetric for EphemeralKey { Some(common_utils::events::ApiEventsType::Miscellaneous) } } + +#[derive( + Clone, + Copy, + Debug, + serde::Serialize, + serde::Deserialize, + strum::Display, + strum::EnumString, + PartialEq, + Eq, +)] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum ResourceType { + Payment, + PaymentMethod, +} diff --git a/crates/diesel_models/src/events.rs b/crates/diesel_models/src/events.rs index b6c5efe0fd1a..82b2b58f80bf 100644 --- a/crates/diesel_models/src/events.rs +++ b/crates/diesel_models/src/events.rs @@ -1,11 +1,7 @@ -use common_utils::{ - crypto::OptionalEncryptableSecretString, custom_serde, encryption::Encryption, - types::keymanager::ToEncryptable, -}; +use common_utils::{custom_serde, encryption::Encryption}; use diesel::{ expression::AsExpression, AsChangeset, Identifiable, Insertable, Queryable, Selectable, }; -use masking::Secret; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -63,38 +59,6 @@ pub struct Event { pub metadata: Option, } -pub struct EventWithEncryption { - pub request: Option, - pub response: Option, -} - -pub struct EncryptableEvent { - pub request: OptionalEncryptableSecretString, - pub response: OptionalEncryptableSecretString, -} - -impl ToEncryptable, Encryption> for EventWithEncryption { - fn to_encryptable(self) -> rustc_hash::FxHashMap { - let mut map = rustc_hash::FxHashMap::default(); - self.request.map(|x| map.insert("request".to_string(), x)); - self.response.map(|x| map.insert("response".to_string(), x)); - map - } - - fn from_encryptable( - mut hashmap: rustc_hash::FxHashMap< - String, - common_utils::crypto::Encryptable>, - >, - ) -> common_utils::errors::CustomResult - { - Ok(EncryptableEvent { - request: hashmap.remove("request"), - response: hashmap.remove("response"), - }) - } -} - #[derive(Clone, Debug, Deserialize, Serialize, AsExpression, diesel::FromSqlRow)] #[diesel(sql_type = diesel::sql_types::Jsonb)] pub enum EventMetadata { diff --git a/crates/diesel_models/src/gsm.rs b/crates/diesel_models/src/gsm.rs index 6a444410fa8f..aba8d75a6ebb 100644 --- a/crates/diesel_models/src/gsm.rs +++ b/crates/diesel_models/src/gsm.rs @@ -1,5 +1,6 @@ //! Gateway status mapping +use common_enums::ErrorCategory; use common_utils::{ custom_serde, events::{ApiEventMetric, ApiEventsType}, @@ -39,6 +40,7 @@ pub struct GatewayStatusMap { pub step_up_possible: bool, pub unified_code: Option, pub unified_message: Option, + pub error_category: Option, } #[derive(Clone, Debug, Eq, PartialEq, Insertable)] @@ -55,17 +57,11 @@ pub struct GatewayStatusMappingNew { pub step_up_possible: bool, pub unified_code: Option, pub unified_message: Option, + pub error_category: Option, } #[derive( - Clone, - Debug, - PartialEq, - Eq, - AsChangeset, - router_derive::DebugAsDisplay, - Default, - serde::Deserialize, + Clone, Debug, PartialEq, Eq, AsChangeset, router_derive::DebugAsDisplay, serde::Deserialize, )] #[diesel(table_name = gateway_status_map)] pub struct GatewayStatusMapperUpdateInternal { @@ -80,6 +76,8 @@ pub struct GatewayStatusMapperUpdateInternal { pub step_up_possible: Option, pub unified_code: Option, pub unified_message: Option, + pub error_category: Option, + pub last_modified: PrimitiveDateTime, } #[derive(Debug)] @@ -90,6 +88,7 @@ pub struct GatewayStatusMappingUpdate { pub step_up_possible: Option, pub unified_code: Option, pub unified_message: Option, + pub error_category: Option, } impl From for GatewayStatusMapperUpdateInternal { @@ -101,6 +100,7 @@ impl From for GatewayStatusMapperUpdateInternal { step_up_possible, unified_code, unified_message, + error_category, } = value; Self { status, @@ -109,7 +109,13 @@ impl From for GatewayStatusMapperUpdateInternal { step_up_possible, unified_code, unified_message, - ..Default::default() + error_category, + last_modified: common_utils::date_time::now(), + connector: None, + flow: None, + sub_flow: None, + code: None, + message: None, } } } diff --git a/crates/diesel_models/src/kv.rs b/crates/diesel_models/src/kv.rs index d91e57a2b5d6..94dcf00f755c 100644 --- a/crates/diesel_models/src/kv.rs +++ b/crates/diesel_models/src/kv.rs @@ -3,12 +3,16 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "v2")] use crate::payment_attempt::PaymentAttemptUpdateInternal; +#[cfg(feature = "v1")] +use crate::payment_intent::PaymentIntentUpdate; +#[cfg(feature = "v2")] +use crate::payment_intent::PaymentIntentUpdateInternal; use crate::{ address::{Address, AddressNew, AddressUpdateInternal}, customers::{Customer, CustomerNew, CustomerUpdateInternal}, errors, payment_attempt::{PaymentAttempt, PaymentAttemptNew, PaymentAttemptUpdate}, - payment_intent::{PaymentIntentNew, PaymentIntentUpdate}, + payment_intent::PaymentIntentNew, payout_attempt::{PayoutAttempt, PayoutAttemptNew, PayoutAttemptUpdate}, payouts::{Payouts, PayoutsNew, PayoutsUpdate}, refund::{Refund, RefundNew, RefundUpdate}, @@ -20,8 +24,8 @@ use crate::{ #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case", tag = "db_op", content = "data")] pub enum DBOperation { - Insert { insertable: Insertable }, - Update { updatable: Updateable }, + Insert { insertable: Box }, + Update { updatable: Box }, } impl DBOperation { @@ -33,7 +37,7 @@ impl DBOperation { } pub fn table<'a>(&self) -> &'a str { match self { - Self::Insert { insertable } => match insertable { + Self::Insert { insertable } => match **insertable { Insertable::PaymentIntent(_) => "payment_intent", Insertable::PaymentAttempt(_) => "payment_attempt", Insertable::Refund(_) => "refund", @@ -45,7 +49,7 @@ impl DBOperation { Insertable::PaymentMethod(_) => "payment_method", Insertable::Mandate(_) => "mandate", }, - Self::Update { updatable } => match updatable { + Self::Update { updatable } => match **updatable { Updateable::PaymentIntentUpdate(_) => "payment_intent", Updateable::PaymentAttemptUpdate(_) => "payment_attempt", Updateable::RefundUpdate(_) => "refund", @@ -83,7 +87,7 @@ pub struct TypedSql { impl DBOperation { pub async fn execute(self, conn: &PgPooledConn) -> crate::StorageResult { Ok(match self { - Self::Insert { insertable } => match insertable { + Self::Insert { insertable } => match *insertable { Insertable::PaymentIntent(a) => { DBResult::PaymentIntent(Box::new(a.insert(conn).await?)) } @@ -107,7 +111,12 @@ impl DBOperation { } Insertable::Mandate(m) => DBResult::Mandate(Box::new(m.insert(conn).await?)), }, - Self::Update { updatable } => match updatable { + Self::Update { updatable } => match *updatable { + #[cfg(feature = "v1")] + Updateable::PaymentIntentUpdate(a) => { + DBResult::PaymentIntent(Box::new(a.orig.update(conn, a.update_data).await?)) + } + #[cfg(feature = "v2")] Updateable::PaymentIntentUpdate(a) => { DBResult::PaymentIntent(Box::new(a.orig.update(conn, a.update_data).await?)) } @@ -170,7 +179,7 @@ impl DBOperation { )), #[cfg(all(feature = "v2", feature = "customer_v2"))] Updateable::CustomerUpdate(cust) => DBResult::Customer(Box::new( - Customer::update_by_id(conn, cust.orig.id.clone(), cust.update_data).await?, + Customer::update_by_id(conn, cust.orig.id, cust.update_data).await?, )), }, }) @@ -201,8 +210,8 @@ impl TypedSql { #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case", tag = "table", content = "data")] pub enum Insertable { - PaymentIntent(PaymentIntentNew), - PaymentAttempt(PaymentAttemptNew), + PaymentIntent(Box), + PaymentAttempt(Box), Refund(RefundNew), Address(Box), Customer(CustomerNew), @@ -216,14 +225,14 @@ pub enum Insertable { #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case", tag = "table", content = "data")] pub enum Updateable { - PaymentIntentUpdate(PaymentIntentUpdateMems), - PaymentAttemptUpdate(PaymentAttemptUpdateMems), + PaymentIntentUpdate(Box), + PaymentAttemptUpdate(Box), RefundUpdate(RefundUpdateMems), CustomerUpdate(CustomerUpdateMems), AddressUpdate(Box), PayoutsUpdate(PayoutsUpdateMems), PayoutAttemptUpdate(PayoutAttemptUpdateMems), - PaymentMethodUpdate(PaymentMethodUpdateMems), + PaymentMethodUpdate(Box), MandateUpdate(MandateUpdateMems), } @@ -238,13 +247,20 @@ pub struct AddressUpdateMems { pub orig: Address, pub update_data: AddressUpdateInternal, } - +#[cfg(feature = "v1")] #[derive(Debug, Serialize, Deserialize)] pub struct PaymentIntentUpdateMems { pub orig: PaymentIntent, pub update_data: PaymentIntentUpdate, } +#[cfg(feature = "v2")] +#[derive(Debug, Serialize, Deserialize)] +pub struct PaymentIntentUpdateMems { + pub orig: PaymentIntent, + pub update_data: PaymentIntentUpdateInternal, +} + #[derive(Debug, Serialize, Deserialize)] pub struct PaymentAttemptUpdateMems { pub orig: PaymentAttempt, diff --git a/crates/diesel_models/src/lib.rs b/crates/diesel_models/src/lib.rs index 598035524a72..1369368a8099 100644 --- a/crates/diesel_models/src/lib.rs +++ b/crates/diesel_models/src/lib.rs @@ -12,6 +12,7 @@ pub mod blocklist; pub mod blocklist_fingerprint; pub mod customers; pub mod dispute; +pub mod dynamic_routing_stats; pub mod enums; pub mod ephemeral_key; pub mod errors; @@ -38,9 +39,11 @@ pub mod payouts; pub mod process_tracker; pub mod query; pub mod refund; +pub mod relay; pub mod reverse_lookup; pub mod role; pub mod routing_algorithm; +pub mod types; pub mod unified_translations; #[allow(unused_qualifications)] @@ -74,7 +77,6 @@ pub use self::{ /// `Option` values. /// /// [diesel-2.0-array-nullability]: https://diesel.rs/guides/migration_guide.html#2-0-0-nullability-of-array-elements - #[doc(hidden)] pub(crate) mod diesel_impl { use diesel::{ @@ -127,11 +129,10 @@ pub(crate) mod diesel_impl { } pub(crate) mod metrics { - use router_env::{counter_metric, global_meter, histogram_metric, metrics_context, once_cell}; + use router_env::{counter_metric, global_meter, histogram_metric_f64, once_cell}; - metrics_context!(CONTEXT); global_meter!(GLOBAL_METER, "ROUTER_API"); counter_metric!(DATABASE_CALLS_COUNT, GLOBAL_METER); - histogram_metric!(DATABASE_CALL_TIME, GLOBAL_METER); + histogram_metric_f64!(DATABASE_CALL_TIME, GLOBAL_METER); } diff --git a/crates/diesel_models/src/merchant_account.rs b/crates/diesel_models/src/merchant_account.rs index b1dcc1d3476a..b5c2bc285724 100644 --- a/crates/diesel_models/src/merchant_account.rs +++ b/crates/diesel_models/src/merchant_account.rs @@ -51,6 +51,7 @@ pub struct MerchantAccount { pub payment_link_config: Option, pub pm_collect_link_config: Option, pub version: common_enums::ApiVersion, + pub is_platform_account: bool, } #[cfg(feature = "v1")] @@ -83,6 +84,7 @@ pub struct MerchantAccountSetter { pub payment_link_config: Option, pub pm_collect_link_config: Option, pub version: common_enums::ApiVersion, + pub is_platform_account: bool, } #[cfg(feature = "v1")] @@ -117,6 +119,7 @@ impl From for MerchantAccount { payment_link_config: item.payment_link_config, pm_collect_link_config: item.pm_collect_link_config, version: item.version, + is_platform_account: item.is_platform_account, } } } @@ -148,6 +151,7 @@ pub struct MerchantAccount { pub recon_status: storage_enums::ReconStatus, pub version: common_enums::ApiVersion, pub id: common_utils::id_type::MerchantId, + pub is_platform_account: bool, } #[cfg(feature = "v2")] @@ -165,6 +169,7 @@ impl From for MerchantAccount { organization_id: item.organization_id, recon_status: item.recon_status, version: item.version, + is_platform_account: item.is_platform_account, } } } @@ -182,6 +187,7 @@ pub struct MerchantAccountSetter { pub organization_id: common_utils::id_type::OrganizationId, pub recon_status: storage_enums::ReconStatus, pub version: common_enums::ApiVersion, + pub is_platform_account: bool, } impl MerchantAccount { @@ -228,6 +234,7 @@ pub struct MerchantAccountNew { pub payment_link_config: Option, pub pm_collect_link_config: Option, pub version: common_enums::ApiVersion, + pub is_platform_account: bool, } #[cfg(feature = "v2")] @@ -244,6 +251,7 @@ pub struct MerchantAccountNew { pub recon_status: storage_enums::ReconStatus, pub id: common_utils::id_type::MerchantId, pub version: common_enums::ApiVersion, + pub is_platform_account: bool, } #[cfg(feature = "v2")] @@ -258,6 +266,39 @@ pub struct MerchantAccountUpdateInternal { pub modified_at: time::PrimitiveDateTime, pub organization_id: Option, pub recon_status: Option, + pub is_platform_account: Option, +} + +#[cfg(feature = "v2")] +impl MerchantAccountUpdateInternal { + pub fn apply_changeset(self, source: MerchantAccount) -> MerchantAccount { + let Self { + merchant_name, + merchant_details, + publishable_key, + storage_scheme, + metadata, + modified_at, + organization_id, + recon_status, + is_platform_account, + } = self; + + MerchantAccount { + merchant_name: merchant_name.or(source.merchant_name), + merchant_details: merchant_details.or(source.merchant_details), + publishable_key: publishable_key.or(source.publishable_key), + storage_scheme: storage_scheme.unwrap_or(source.storage_scheme), + metadata: metadata.or(source.metadata), + created_at: source.created_at, + modified_at, + organization_id: organization_id.unwrap_or(source.organization_id), + recon_status: recon_status.unwrap_or(source.recon_status), + version: source.version, + id: source.id, + is_platform_account: is_platform_account.unwrap_or(source.is_platform_account), + } + } } #[cfg(feature = "v1")] @@ -289,4 +330,75 @@ pub struct MerchantAccountUpdateInternal { pub recon_status: Option, pub payment_link_config: Option, pub pm_collect_link_config: Option, + pub is_platform_account: Option, +} + +#[cfg(feature = "v1")] +impl MerchantAccountUpdateInternal { + pub fn apply_changeset(self, source: MerchantAccount) -> MerchantAccount { + let Self { + merchant_name, + merchant_details, + return_url, + webhook_details, + sub_merchants_enabled, + parent_merchant_id, + enable_payment_response_hash, + payment_response_hash_key, + redirect_to_merchant_with_http_post, + publishable_key, + storage_scheme, + locker_id, + metadata, + routing_algorithm, + primary_business_details, + modified_at, + intent_fulfillment_time, + frm_routing_algorithm, + payout_routing_algorithm, + organization_id, + is_recon_enabled, + default_profile, + recon_status, + payment_link_config, + pm_collect_link_config, + is_platform_account, + } = self; + + MerchantAccount { + merchant_id: source.merchant_id, + return_url: return_url.or(source.return_url), + enable_payment_response_hash: enable_payment_response_hash + .unwrap_or(source.enable_payment_response_hash), + payment_response_hash_key: payment_response_hash_key + .or(source.payment_response_hash_key), + redirect_to_merchant_with_http_post: redirect_to_merchant_with_http_post + .unwrap_or(source.redirect_to_merchant_with_http_post), + merchant_name: merchant_name.or(source.merchant_name), + merchant_details: merchant_details.or(source.merchant_details), + webhook_details: webhook_details.or(source.webhook_details), + sub_merchants_enabled: sub_merchants_enabled.or(source.sub_merchants_enabled), + parent_merchant_id: parent_merchant_id.or(source.parent_merchant_id), + publishable_key: publishable_key.or(source.publishable_key), + storage_scheme: storage_scheme.unwrap_or(source.storage_scheme), + locker_id: locker_id.or(source.locker_id), + metadata: metadata.or(source.metadata), + routing_algorithm: routing_algorithm.or(source.routing_algorithm), + primary_business_details: primary_business_details + .unwrap_or(source.primary_business_details), + intent_fulfillment_time: intent_fulfillment_time.or(source.intent_fulfillment_time), + created_at: source.created_at, + modified_at, + frm_routing_algorithm: frm_routing_algorithm.or(source.frm_routing_algorithm), + payout_routing_algorithm: payout_routing_algorithm.or(source.payout_routing_algorithm), + organization_id: organization_id.unwrap_or(source.organization_id), + is_recon_enabled: is_recon_enabled.unwrap_or(source.is_recon_enabled), + default_profile: default_profile.unwrap_or(source.default_profile), + recon_status: recon_status.unwrap_or(source.recon_status), + payment_link_config: payment_link_config.or(source.payment_link_config), + pm_collect_link_config: pm_collect_link_config.or(source.pm_collect_link_config), + version: source.version, + is_platform_account: is_platform_account.unwrap_or(source.is_platform_account), + } + } } diff --git a/crates/diesel_models/src/merchant_connector_account.rs b/crates/diesel_models/src/merchant_connector_account.rs index f78886b8c936..1ecff0fc70a0 100644 --- a/crates/diesel_models/src/merchant_connector_account.rs +++ b/crates/diesel_models/src/merchant_connector_account.rs @@ -76,8 +76,8 @@ pub struct MerchantConnectorAccount { pub connector_name: String, pub connector_account_details: Encryption, pub disabled: Option, - #[diesel(deserialize_as = super::OptionalDieselArray)] - pub payment_methods_enabled: Option>, + #[diesel(deserialize_as = super::OptionalDieselArray)] + pub payment_methods_enabled: Option>, pub connector_type: storage_enums::ConnectorType, pub metadata: Option, pub connector_label: Option, @@ -146,7 +146,8 @@ pub struct MerchantConnectorAccountNew { pub connector_name: Option, pub connector_account_details: Option, pub disabled: Option, - pub payment_methods_enabled: Option>, + #[diesel(deserialize_as = super::OptionalDieselArray)] + pub payment_methods_enabled: Option>, pub metadata: Option, pub connector_label: Option, pub created_at: time::PrimitiveDateTime, @@ -199,7 +200,8 @@ pub struct MerchantConnectorAccountUpdateInternal { pub connector_account_details: Option, pub connector_label: Option, pub disabled: Option, - pub payment_methods_enabled: Option>, + #[diesel(deserialize_as = super::OptionalDieselArray)] + pub payment_methods_enabled: Option>, pub metadata: Option, pub modified_at: Option, pub connector_webhook_details: Option, diff --git a/crates/diesel_models/src/organization.rs b/crates/diesel_models/src/organization.rs index bd4fd1192017..6a3cad24e1c6 100644 --- a/crates/diesel_models/src/organization.rs +++ b/crates/diesel_models/src/organization.rs @@ -197,8 +197,8 @@ impl From for OrganizationUpdateInternal { } } } -#[cfg(feature = "v2")] +#[cfg(feature = "v2")] impl From for OrganizationUpdateInternal { fn from(value: OrganizationUpdate) -> Self { match value { diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index 75a9aeda2b86..03facc2ebab4 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -1,14 +1,35 @@ -use common_utils::{id_type, pii, types::MinorUnit}; +use common_utils::{ + id_type, pii, + types::{ConnectorTransactionId, ConnectorTransactionIdTrait, MinorUnit}, +}; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; -use crate::enums::{self as storage_enums}; +use crate::enums as storage_enums; #[cfg(feature = "v1")] use crate::schema::payment_attempt; #[cfg(feature = "v2")] use crate::schema_v2::payment_attempt; +common_utils::impl_to_sql_from_sql_json!(ConnectorMandateReferenceId); +#[derive( + Clone, Debug, serde::Deserialize, serde::Serialize, Eq, PartialEq, diesel::AsExpression, +)] +#[diesel(sql_type = diesel::sql_types::Jsonb)] +pub struct ConnectorMandateReferenceId { + pub connector_mandate_id: Option, + pub payment_method_id: Option, + pub mandate_metadata: Option, + pub connector_mandate_request_reference_id: Option, +} + +impl ConnectorMandateReferenceId { + pub fn get_connector_mandate_request_reference_id(&self) -> Option { + self.connector_mandate_request_reference_id.clone() + } +} + #[cfg(feature = "v2")] #[derive( Clone, Debug, Eq, PartialEq, Identifiable, Queryable, Serialize, Deserialize, Selectable, @@ -21,9 +42,8 @@ pub struct PaymentAttempt { pub connector: Option, pub error_message: Option, pub surcharge_amount: Option, - pub payment_method_id: Option, - pub confirm: bool, - pub authentication_type: Option, + pub payment_method_id: Option, + pub authentication_type: storage_enums::AuthenticationType, #[serde(with = "common_utils::custom_serde::iso8601")] pub created_at: PrimitiveDateTime, #[serde(with = "common_utils::custom_serde::iso8601")] @@ -32,12 +52,12 @@ pub struct PaymentAttempt { pub last_synced: Option, pub cancellation_reason: Option, pub amount_to_capture: Option, - pub browser_info: Option, + pub browser_info: Option, pub error_code: Option, pub payment_token: Option, - pub connector_metadata: Option, + pub connector_metadata: Option, pub payment_experience: Option, - pub payment_method_data: Option, + pub payment_method_data: Option, pub preprocessing_step_id: Option, pub error_reason: Option, pub multiple_capture_count: Option, @@ -45,16 +65,14 @@ pub struct PaymentAttempt { pub amount_capturable: MinorUnit, pub updated_by: String, pub merchant_connector_id: Option, - pub authentication_data: Option, - pub encoded_data: Option, + pub encoded_data: Option>, pub unified_code: Option, pub unified_message: Option, - pub net_amount: Option, + pub net_amount: MinorUnit, pub external_three_ds_authentication_attempted: Option, pub authentication_connector: Option, pub authentication_id: Option, pub fingerprint_id: Option, - pub payment_method_billing_address_id: Option, pub charge_id: Option, pub client_source: Option, pub client_version: Option, @@ -62,16 +80,20 @@ pub struct PaymentAttempt { pub profile_id: id_type::ProfileId, pub organization_id: id_type::OrganizationId, pub card_network: Option, - pub payment_method_type_v2: Option, - pub connector_payment_id: Option, - pub payment_method_subtype: Option, + pub payment_method_type_v2: storage_enums::PaymentMethod, + pub connector_payment_id: Option, + pub payment_method_subtype: storage_enums::PaymentMethodType, pub routing_result: Option, pub authentication_applied: Option, pub external_reference_id: Option, pub tax_on_surcharge: Option, - pub id: String, + pub payment_method_billing_address: Option, + pub redirection_data: Option, + pub connector_payment_data: Option, + pub id: id_type::GlobalAttemptId, pub shipping_cost: Option, pub order_tax_amount: Option, + pub connector_mandate_detail: Option, } #[cfg(feature = "v1")] @@ -94,7 +116,7 @@ pub struct PaymentAttempt { pub tax_amount: Option, pub payment_method_id: Option, pub payment_method: Option, - pub connector_transaction_id: Option, + pub connector_transaction_id: Option, pub capture_method: Option, #[serde(default, with = "common_utils::custom_serde::iso8601::option")] pub capture_on: Option, @@ -148,18 +170,47 @@ pub struct PaymentAttempt { pub card_network: Option, pub shipping_cost: Option, pub order_tax_amount: Option, + pub connector_transaction_data: Option, + pub connector_mandate_detail: Option, } #[cfg(feature = "v1")] -impl PaymentAttempt { - pub fn get_or_calculate_net_amount(&self) -> MinorUnit { - self.net_amount.unwrap_or( - self.amount - + self.surcharge_amount.unwrap_or(MinorUnit::new(0)) - + self.tax_amount.unwrap_or(MinorUnit::new(0)) - + self.shipping_cost.unwrap_or(MinorUnit::new(0)) - + self.order_tax_amount.unwrap_or(MinorUnit::new(0)), - ) +impl ConnectorTransactionIdTrait for PaymentAttempt { + fn get_optional_connector_transaction_id(&self) -> Option<&String> { + match self + .connector_transaction_id + .as_ref() + .map(|txn_id| txn_id.get_txn_id(self.connector_transaction_data.as_ref())) + .transpose() + { + Ok(txn_id) => txn_id, + + // In case hashed data is missing from DB, use the hashed ID as connector transaction ID + Err(_) => self + .connector_transaction_id + .as_ref() + .map(|txn_id| txn_id.get_id()), + } + } +} + +#[cfg(feature = "v2")] +impl ConnectorTransactionIdTrait for PaymentAttempt { + fn get_optional_connector_transaction_id(&self) -> Option<&String> { + match self + .connector_payment_id + .as_ref() + .map(|txn_id| txn_id.get_txn_id(self.connector_payment_data.as_ref())) + .transpose() + { + Ok(txn_id) => txn_id, + + // In case hashed data is missing from DB, use the hashed ID as connector payment ID + Err(_) => self + .connector_payment_id + .as_ref() + .map(|txn_id| txn_id.get_id()), + } } } @@ -181,9 +232,8 @@ pub struct PaymentAttemptNew { pub error_message: Option, pub surcharge_amount: Option, pub tax_on_surcharge: Option, - pub payment_method_id: Option, - pub confirm: bool, - pub authentication_type: Option, + pub payment_method_id: Option, + pub authentication_type: storage_enums::AuthenticationType, #[serde(with = "common_utils::custom_serde::iso8601")] pub created_at: PrimitiveDateTime, #[serde(with = "common_utils::custom_serde::iso8601")] @@ -192,12 +242,12 @@ pub struct PaymentAttemptNew { pub last_synced: Option, pub cancellation_reason: Option, pub amount_to_capture: Option, - pub browser_info: Option, + pub browser_info: Option, pub payment_token: Option, pub error_code: Option, - pub connector_metadata: Option, + pub connector_metadata: Option, pub payment_experience: Option, - pub payment_method_data: Option, + pub payment_method_data: Option, pub preprocessing_step_id: Option, pub error_reason: Option, pub connector_response_reference_id: Option, @@ -205,16 +255,16 @@ pub struct PaymentAttemptNew { pub amount_capturable: MinorUnit, pub updated_by: String, pub merchant_connector_id: Option, - pub authentication_data: Option, - pub encoded_data: Option, + pub redirection_data: Option, + pub encoded_data: Option>, pub unified_code: Option, pub unified_message: Option, - pub net_amount: Option, + pub net_amount: MinorUnit, pub external_three_ds_authentication_attempted: Option, pub authentication_connector: Option, pub authentication_id: Option, pub fingerprint_id: Option, - pub payment_method_billing_address_id: Option, + pub payment_method_billing_address: Option, pub charge_id: Option, pub client_source: Option, pub client_version: Option, @@ -224,6 +274,10 @@ pub struct PaymentAttemptNew { pub card_network: Option, pub shipping_cost: Option, pub order_tax_amount: Option, + pub payment_method_type_v2: storage_enums::PaymentMethod, + pub payment_method_subtype: storage_enums::PaymentMethodType, + pub id: id_type::GlobalAttemptId, + pub connector_mandate_detail: Option, } #[cfg(feature = "v1")] @@ -296,47 +350,7 @@ pub struct PaymentAttemptNew { pub card_network: Option, pub shipping_cost: Option, pub order_tax_amount: Option, -} - -#[cfg(feature = "v1")] -impl PaymentAttemptNew { - /// returns amount + surcharge_amount + tax_amount (surcharge) + shipping_cost + order_tax_amount - pub fn calculate_net_amount(&self) -> MinorUnit { - self.amount - + self.surcharge_amount.unwrap_or(MinorUnit::new(0)) - + self.tax_amount.unwrap_or(MinorUnit::new(0)) - + self.shipping_cost.unwrap_or(MinorUnit::new(0)) - } - - pub fn get_or_calculate_net_amount(&self) -> MinorUnit { - self.net_amount - .unwrap_or_else(|| self.calculate_net_amount()) - } - - pub fn populate_derived_fields(self) -> Self { - let mut payment_attempt_new = self; - payment_attempt_new.net_amount = Some(payment_attempt_new.calculate_net_amount()); - payment_attempt_new - } -} - -#[cfg(feature = "v2")] -impl PaymentAttemptNew { - /// returns amount + surcharge_amount + tax_amount - pub fn calculate_net_amount(&self) -> MinorUnit { - todo!(); - } - - pub fn get_or_calculate_net_amount(&self) -> MinorUnit { - self.net_amount - .unwrap_or_else(|| self.calculate_net_amount()) - } - - pub fn populate_derived_fields(self) -> Self { - let mut payment_attempt_new = self; - payment_attempt_new.net_amount = Some(payment_attempt_new.calculate_net_amount()); - payment_attempt_new - } + pub connector_mandate_detail: Option, } #[cfg(feature = "v1")] @@ -408,6 +422,7 @@ pub enum PaymentAttemptUpdate { customer_acceptance: Option, shipping_cost: Option, order_tax_amount: Option, + connector_mandate_detail: Option, }, VoidUpdate { status: storage_enums::AttemptStatus, @@ -418,6 +433,10 @@ pub enum PaymentAttemptUpdate { payment_method_id: Option, updated_by: String, }, + ConnectorMandateDetailUpdate { + connector_mandate_detail: Option, + updated_by: String, + }, BlocklistUpdate { status: storage_enums::AttemptStatus, error_code: Option>, @@ -451,6 +470,7 @@ pub enum PaymentAttemptUpdate { unified_message: Option>, payment_method_data: Option, charge_id: Option, + connector_mandate_detail: Option, }, UnresolvedResponseUpdate { status: storage_enums::AttemptStatus, @@ -529,6 +549,10 @@ pub enum PaymentAttemptUpdate { unified_message: Option, connector_transaction_id: Option, }, + PostSessionTokensUpdate { + updated_by: String, + connector_metadata: Option, + }, } #[cfg(feature = "v2")] @@ -608,6 +632,10 @@ pub enum PaymentAttemptUpdate { // payment_method_id: Option, // updated_by: String, // }, + // ConnectorMandateDetailUpdate { + // connector_mandate_detail: Option, + // updated_by: String, + // } // BlocklistUpdate { // status: storage_enums::AttemptStatus, // error_code: Option>, @@ -721,46 +749,48 @@ pub enum PaymentAttemptUpdate { // }, } +// TODO: uncomment fields as and when required #[cfg(feature = "v2")] #[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)] #[diesel(table_name = payment_attempt)] pub struct PaymentAttemptUpdateInternal { - net_amount: Option, - status: Option, - authentication_type: Option, - error_message: Option>, - payment_method_id: Option, - cancellation_reason: Option, - modified_at: PrimitiveDateTime, - browser_info: Option, - payment_token: Option, - error_code: Option>, - connector_metadata: Option, - payment_method_data: Option, - payment_experience: Option, - preprocessing_step_id: Option, - error_reason: Option>, - connector_response_reference_id: Option, - multiple_capture_count: Option, - surcharge_amount: Option, - tax_on_surcharge: Option, - amount_capturable: Option, - updated_by: String, - merchant_connector_id: Option>, - authentication_data: Option, - encoded_data: Option, - unified_code: Option>, - unified_message: Option>, - external_three_ds_authentication_attempted: Option, - authentication_connector: Option, - authentication_id: Option, - fingerprint_id: Option, - payment_method_billing_address_id: Option, - charge_id: Option, - client_source: Option, - client_version: Option, - customer_acceptance: Option, - card_network: Option, + pub status: Option, + // authentication_type: Option, + pub error_message: Option, + pub connector_payment_id: Option, + // payment_method_id: Option, + // cancellation_reason: Option, + pub modified_at: PrimitiveDateTime, + pub browser_info: Option, + // payment_token: Option, + pub error_code: Option, + pub connector_metadata: Option, + // payment_method_data: Option, + // payment_experience: Option, + // preprocessing_step_id: Option, + pub error_reason: Option, + // connector_response_reference_id: Option, + // multiple_capture_count: Option, + // pub surcharge_amount: Option, + // tax_on_surcharge: Option, + pub amount_capturable: Option, + pub amount_to_capture: Option, + pub updated_by: String, + pub merchant_connector_id: Option, + pub connector: Option, + pub redirection_data: Option, + // encoded_data: Option, + pub unified_code: Option>, + pub unified_message: Option>, + // external_three_ds_authentication_attempted: Option, + // authentication_connector: Option, + // authentication_id: Option, + // fingerprint_id: Option, + // charge_id: Option, + // client_source: Option, + // client_version: Option, + // customer_acceptance: Option, + // card_network: Option, } #[cfg(feature = "v1")] @@ -771,7 +801,7 @@ pub struct PaymentAttemptUpdateInternal { pub net_amount: Option, pub currency: Option, pub status: Option, - pub connector_transaction_id: Option, + pub connector_transaction_id: Option, pub amount_to_capture: Option, pub connector: Option>, pub authentication_type: Option, @@ -816,13 +846,8 @@ pub struct PaymentAttemptUpdateInternal { pub card_network: Option, pub shipping_cost: Option, pub order_tax_amount: Option, -} - -#[cfg(feature = "v2")] -impl PaymentAttemptUpdateInternal { - pub fn populate_derived_fields(self, source: &PaymentAttempt) -> Self { - todo!(); - } + pub connector_transaction_data: Option, + pub connector_mandate_detail: Option, } #[cfg(feature = "v1")] @@ -1004,6 +1029,8 @@ impl PaymentAttemptUpdate { card_network, shipping_cost, order_tax_amount, + connector_transaction_data, + connector_mandate_detail, } = PaymentAttemptUpdateInternal::from(self).populate_derived_fields(&source); PaymentAttempt { amount: amount.unwrap_or(source.amount), @@ -1059,6 +1086,9 @@ impl PaymentAttemptUpdate { card_network: card_network.or(source.card_network), shipping_cost: shipping_cost.or(source.shipping_cost), order_tax_amount: order_tax_amount.or(source.order_tax_amount), + connector_transaction_data: connector_transaction_data + .or(source.connector_transaction_data), + connector_mandate_detail: connector_mandate_detail.or(source.connector_mandate_detail), ..source } } @@ -1371,7 +1401,63 @@ impl From for PaymentAttemptUpdateInternal { // customer_acceptance: None, // card_network: None, // }, - // PaymentAttemptUpdate::PaymentMethodDetailsUpdate { + // PaymentAttemptUpdate::ConnectorMandateDetailUpdate { + // connector_mandate_detail, + // updated_by, + // } => Self { + // payment_method_id: None, + // modified_at: common_utils::date_time::now(), + // updated_by, + // amount: None, + // net_amount: None, + // currency: None, + // status: None, + // connector_transaction_id: None, + // amount_to_capture: None, + // connector: None, + // authentication_type: None, + // payment_method: None, + // error_message: None, + // cancellation_reason: None, + // mandate_id: None, + // browser_info: None, + // payment_token: None, + // error_code: None, + // connector_metadata: None, + // payment_method_data: None, + // payment_method_type: None, + // payment_experience: None, + // business_sub_label: None, + // straight_through_algorithm: None, + // preprocessing_step_id: None, + // error_reason: None, + // capture_method: None, + // connector_response_reference_id: None, + // multiple_capture_count: None, + // surcharge_amount: None, + // tax_amount: None, + // amount_capturable: None, + // merchant_connector_id: None, + // authentication_data: None, + // encoded_data: None, + // unified_code: None, + // unified_message: None, + // external_three_ds_authentication_attempted: None, + // authentication_connector: None, + // authentication_id: None, + // fingerprint_id: None, + // payment_method_billing_address_id: None, + // charge_id: None, + // client_source: None, + // client_version: None, + // customer_acceptance: None, + // card_network: None, + // shipping_cost: None, + // order_tax_amount: None, + // connector_transaction_data: None, + // connector_mandate_detail, + // }, + // PaymentAttemptUpdate::ConnectorMandateDetailUpdate { // payment_method_id, // updated_by, // } => Self { @@ -2053,6 +2139,8 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_transaction_data: None, + connector_mandate_detail: None, }, PaymentAttemptUpdate::AuthenticationTypeUpdate { authentication_type, @@ -2107,6 +2195,8 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_transaction_data: None, + connector_mandate_detail: None, }, PaymentAttemptUpdate::ConfirmUpdate { amount, @@ -2141,6 +2231,7 @@ impl From for PaymentAttemptUpdateInternal { customer_acceptance, shipping_cost, order_tax_amount, + connector_mandate_detail, } => Self { amount: Some(amount), currency: Some(currency), @@ -2191,6 +2282,8 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost, order_tax_amount, + connector_transaction_data: None, + connector_mandate_detail, }, PaymentAttemptUpdate::VoidUpdate { status, @@ -2246,6 +2339,8 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_transaction_data: None, + connector_mandate_detail: None, }, PaymentAttemptUpdate::RejectUpdate { status, @@ -2302,6 +2397,8 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_transaction_data: None, + connector_mandate_detail: None, }, PaymentAttemptUpdate::BlocklistUpdate { status, @@ -2358,12 +2455,14 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_transaction_data: None, + connector_mandate_detail: None, }, - PaymentAttemptUpdate::PaymentMethodDetailsUpdate { - payment_method_id, + PaymentAttemptUpdate::ConnectorMandateDetailUpdate { + connector_mandate_detail, updated_by, } => Self { - payment_method_id, + payment_method_id: None, modified_at: common_utils::date_time::now(), updated_by, amount: None, @@ -2412,98 +2511,152 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_transaction_data: None, + connector_mandate_detail, }, - PaymentAttemptUpdate::ResponseUpdate { - status, - connector, - connector_transaction_id, - authentication_type, + PaymentAttemptUpdate::PaymentMethodDetailsUpdate { payment_method_id, - mandate_id, - connector_metadata, - payment_token, - error_code, - error_message, - error_reason, - connector_response_reference_id, - amount_capturable, updated_by, - authentication_data, - encoded_data, - unified_code, - unified_message, - payment_method_data, - charge_id, } => Self { - status: Some(status), - connector: connector.map(Some), - connector_transaction_id, - authentication_type, payment_method_id, modified_at: common_utils::date_time::now(), - mandate_id, - connector_metadata, - error_code, - error_message, - payment_token, - error_reason, - connector_response_reference_id, - amount_capturable, updated_by, - authentication_data, - encoded_data, - unified_code, - unified_message, - payment_method_data, - charge_id, amount: None, net_amount: None, currency: None, + status: None, + connector_transaction_id: None, amount_to_capture: None, + connector: None, + authentication_type: None, payment_method: None, + error_message: None, cancellation_reason: None, + mandate_id: None, browser_info: None, + payment_token: None, + error_code: None, + connector_metadata: None, + payment_method_data: None, payment_method_type: None, payment_experience: None, business_sub_label: None, straight_through_algorithm: None, preprocessing_step_id: None, + error_reason: None, capture_method: None, + connector_response_reference_id: None, multiple_capture_count: None, surcharge_amount: None, tax_amount: None, + amount_capturable: None, merchant_connector_id: None, + authentication_data: None, + encoded_data: None, + unified_code: None, + unified_message: None, external_three_ds_authentication_attempted: None, authentication_connector: None, authentication_id: None, fingerprint_id: None, payment_method_billing_address_id: None, + charge_id: None, client_source: None, client_version: None, customer_acceptance: None, card_network: None, shipping_cost: None, order_tax_amount: None, + connector_transaction_data: None, + connector_mandate_detail: None, }, - PaymentAttemptUpdate::ErrorUpdate { - connector, + PaymentAttemptUpdate::ResponseUpdate { status, + connector, + connector_transaction_id, + authentication_type, + payment_method_id, + mandate_id, + connector_metadata, + payment_token, error_code, error_message, error_reason, + connector_response_reference_id, amount_capturable, updated_by, + authentication_data, + encoded_data, unified_code, unified_message, - connector_transaction_id, payment_method_data, - authentication_type, - } => Self { - connector: connector.map(Some), - status: Some(status), - error_message, + charge_id, + connector_mandate_detail, + } => { + let (connector_transaction_id, connector_transaction_data) = + connector_transaction_id + .map(ConnectorTransactionId::form_id_and_data) + .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) + .unwrap_or((None, None)); + Self { + status: Some(status), + connector: connector.map(Some), + connector_transaction_id, + authentication_type, + payment_method_id, + modified_at: common_utils::date_time::now(), + mandate_id, + connector_metadata, + error_code, + error_message, + payment_token, + error_reason, + connector_response_reference_id, + amount_capturable, + updated_by, + authentication_data, + encoded_data, + unified_code, + unified_message, + payment_method_data, + charge_id, + connector_transaction_data, + amount: None, + net_amount: None, + currency: None, + amount_to_capture: None, + payment_method: None, + cancellation_reason: None, + browser_info: None, + payment_method_type: None, + payment_experience: None, + business_sub_label: None, + straight_through_algorithm: None, + preprocessing_step_id: None, + capture_method: None, + multiple_capture_count: None, + surcharge_amount: None, + tax_amount: None, + merchant_connector_id: None, + external_three_ds_authentication_attempted: None, + authentication_connector: None, + authentication_id: None, + fingerprint_id: None, + payment_method_billing_address_id: None, + client_source: None, + client_version: None, + customer_acceptance: None, + card_network: None, + shipping_cost: None, + order_tax_amount: None, + connector_mandate_detail, + } + } + PaymentAttemptUpdate::ErrorUpdate { + connector, + status, error_code, - modified_at: common_utils::date_time::now(), + error_message, error_reason, amount_capturable, updated_by, @@ -2512,43 +2665,66 @@ impl From for PaymentAttemptUpdateInternal { connector_transaction_id, payment_method_data, authentication_type, - amount: None, - net_amount: None, - currency: None, - amount_to_capture: None, - payment_method: None, - payment_method_id: None, - cancellation_reason: None, - mandate_id: None, - browser_info: None, - payment_token: None, - connector_metadata: None, - payment_method_type: None, - payment_experience: None, - business_sub_label: None, - straight_through_algorithm: None, - preprocessing_step_id: None, - capture_method: None, - connector_response_reference_id: None, - multiple_capture_count: None, - surcharge_amount: None, - tax_amount: None, - merchant_connector_id: None, - authentication_data: None, - encoded_data: None, - external_three_ds_authentication_attempted: None, - authentication_connector: None, - authentication_id: None, - fingerprint_id: None, - payment_method_billing_address_id: None, - charge_id: None, - client_source: None, - client_version: None, - customer_acceptance: None, - card_network: None, - shipping_cost: None, - order_tax_amount: None, - }, + } => { + let (connector_transaction_id, connector_transaction_data) = + connector_transaction_id + .map(ConnectorTransactionId::form_id_and_data) + .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) + .unwrap_or((None, None)); + Self { + connector: connector.map(Some), + status: Some(status), + error_message, + error_code, + modified_at: common_utils::date_time::now(), + error_reason, + amount_capturable, + updated_by, + unified_code, + unified_message, + connector_transaction_id, + payment_method_data, + authentication_type, + connector_transaction_data, + amount: None, + net_amount: None, + currency: None, + amount_to_capture: None, + payment_method: None, + payment_method_id: None, + cancellation_reason: None, + mandate_id: None, + browser_info: None, + payment_token: None, + connector_metadata: None, + payment_method_type: None, + payment_experience: None, + business_sub_label: None, + straight_through_algorithm: None, + preprocessing_step_id: None, + capture_method: None, + connector_response_reference_id: None, + multiple_capture_count: None, + surcharge_amount: None, + tax_amount: None, + merchant_connector_id: None, + authentication_data: None, + encoded_data: None, + external_three_ds_authentication_attempted: None, + authentication_connector: None, + authentication_id: None, + fingerprint_id: None, + payment_method_billing_address_id: None, + charge_id: None, + client_source: None, + client_version: None, + customer_acceptance: None, + card_network: None, + shipping_cost: None, + order_tax_amount: None, + connector_mandate_detail: None, + } + } PaymentAttemptUpdate::StatusUpdate { status, updated_by } => Self { status: Some(status), modified_at: common_utils::date_time::now(), @@ -2599,6 +2775,8 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_transaction_data: None, + connector_mandate_detail: None, }, PaymentAttemptUpdate::UpdateTrackers { payment_token, @@ -2659,6 +2837,8 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_transaction_data: None, + connector_mandate_detail: None, }, PaymentAttemptUpdate::UnresolvedResponseUpdate { status, @@ -2670,57 +2850,66 @@ impl From for PaymentAttemptUpdateInternal { error_reason, connector_response_reference_id, updated_by, - } => Self { - status: Some(status), - connector: connector.map(Some), - connector_transaction_id, - payment_method_id, - modified_at: common_utils::date_time::now(), - error_code, - error_message, - error_reason, - connector_response_reference_id, - updated_by, - amount: None, - net_amount: None, - currency: None, - amount_to_capture: None, - authentication_type: None, - payment_method: None, - cancellation_reason: None, - mandate_id: None, - browser_info: None, - payment_token: None, - connector_metadata: None, - payment_method_data: None, - payment_method_type: None, - payment_experience: None, - business_sub_label: None, - straight_through_algorithm: None, - preprocessing_step_id: None, - capture_method: None, - multiple_capture_count: None, - surcharge_amount: None, - tax_amount: None, - amount_capturable: None, - merchant_connector_id: None, - authentication_data: None, - encoded_data: None, - unified_code: None, - unified_message: None, - external_three_ds_authentication_attempted: None, - authentication_connector: None, - authentication_id: None, - fingerprint_id: None, - payment_method_billing_address_id: None, - charge_id: None, - client_source: None, - client_version: None, - customer_acceptance: None, - card_network: None, - shipping_cost: None, - order_tax_amount: None, - }, + } => { + let (connector_transaction_id, connector_transaction_data) = + connector_transaction_id + .map(ConnectorTransactionId::form_id_and_data) + .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) + .unwrap_or((None, None)); + Self { + status: Some(status), + connector: connector.map(Some), + connector_transaction_id, + payment_method_id, + modified_at: common_utils::date_time::now(), + error_code, + error_message, + error_reason, + connector_response_reference_id, + updated_by, + connector_transaction_data, + amount: None, + net_amount: None, + currency: None, + amount_to_capture: None, + authentication_type: None, + payment_method: None, + cancellation_reason: None, + mandate_id: None, + browser_info: None, + payment_token: None, + connector_metadata: None, + payment_method_data: None, + payment_method_type: None, + payment_experience: None, + business_sub_label: None, + straight_through_algorithm: None, + preprocessing_step_id: None, + capture_method: None, + multiple_capture_count: None, + surcharge_amount: None, + tax_amount: None, + amount_capturable: None, + merchant_connector_id: None, + authentication_data: None, + encoded_data: None, + unified_code: None, + unified_message: None, + external_three_ds_authentication_attempted: None, + authentication_connector: None, + authentication_id: None, + fingerprint_id: None, + payment_method_billing_address_id: None, + charge_id: None, + client_source: None, + client_version: None, + customer_acceptance: None, + card_network: None, + shipping_cost: None, + order_tax_amount: None, + connector_mandate_detail: None, + } + } PaymentAttemptUpdate::PreprocessingUpdate { status, payment_method_id, @@ -2729,57 +2918,66 @@ impl From for PaymentAttemptUpdateInternal { connector_transaction_id, connector_response_reference_id, updated_by, - } => Self { - status: Some(status), - payment_method_id, - modified_at: common_utils::date_time::now(), - connector_metadata, - preprocessing_step_id, - connector_transaction_id, - connector_response_reference_id, - updated_by, - amount: None, - net_amount: None, - currency: None, - amount_to_capture: None, - connector: None, - authentication_type: None, - payment_method: None, - error_message: None, - cancellation_reason: None, - mandate_id: None, - browser_info: None, - payment_token: None, - error_code: None, - payment_method_data: None, - payment_method_type: None, - payment_experience: None, - business_sub_label: None, - straight_through_algorithm: None, - error_reason: None, - capture_method: None, - multiple_capture_count: None, - surcharge_amount: None, - tax_amount: None, - amount_capturable: None, - merchant_connector_id: None, - authentication_data: None, - encoded_data: None, - unified_code: None, - unified_message: None, - external_three_ds_authentication_attempted: None, - authentication_connector: None, - authentication_id: None, - fingerprint_id: None, - payment_method_billing_address_id: None, - charge_id: None, - client_source: None, - client_version: None, - customer_acceptance: None, - card_network: None, - shipping_cost: None, - order_tax_amount: None, - }, + } => { + let (connector_transaction_id, connector_transaction_data) = + connector_transaction_id + .map(ConnectorTransactionId::form_id_and_data) + .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) + .unwrap_or((None, None)); + Self { + status: Some(status), + payment_method_id, + modified_at: common_utils::date_time::now(), + connector_metadata, + preprocessing_step_id, + connector_transaction_id, + connector_response_reference_id, + updated_by, + connector_transaction_data, + amount: None, + net_amount: None, + currency: None, + amount_to_capture: None, + connector: None, + authentication_type: None, + payment_method: None, + error_message: None, + cancellation_reason: None, + mandate_id: None, + browser_info: None, + payment_token: None, + error_code: None, + payment_method_data: None, + payment_method_type: None, + payment_experience: None, + business_sub_label: None, + straight_through_algorithm: None, + error_reason: None, + capture_method: None, + multiple_capture_count: None, + surcharge_amount: None, + tax_amount: None, + amount_capturable: None, + merchant_connector_id: None, + authentication_data: None, + encoded_data: None, + unified_code: None, + unified_message: None, + external_three_ds_authentication_attempted: None, + authentication_connector: None, + authentication_id: None, + fingerprint_id: None, + payment_method_billing_address_id: None, + charge_id: None, + client_source: None, + client_version: None, + customer_acceptance: None, + card_network: None, + shipping_cost: None, + order_tax_amount: None, + connector_mandate_detail: None, + } + } PaymentAttemptUpdate::CaptureUpdate { multiple_capture_count, updated_by, @@ -2834,6 +3032,8 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_transaction_data: None, + connector_mandate_detail: None, }, PaymentAttemptUpdate::AmountToCaptureUpdate { status, @@ -2889,6 +3089,8 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_transaction_data: None, + connector_mandate_detail: None, }, PaymentAttemptUpdate::ConnectorResponse { authentication_data, @@ -2897,57 +3099,66 @@ impl From for PaymentAttemptUpdateInternal { connector, updated_by, charge_id, - } => Self { - authentication_data, - encoded_data, - connector_transaction_id, - connector: connector.map(Some), - modified_at: common_utils::date_time::now(), - updated_by, - charge_id, - amount: None, - net_amount: None, - currency: None, - status: None, - amount_to_capture: None, - authentication_type: None, - payment_method: None, - error_message: None, - payment_method_id: None, - cancellation_reason: None, - mandate_id: None, - browser_info: None, - payment_token: None, - error_code: None, - connector_metadata: None, - payment_method_data: None, - payment_method_type: None, - payment_experience: None, - business_sub_label: None, - straight_through_algorithm: None, - preprocessing_step_id: None, - error_reason: None, - capture_method: None, - connector_response_reference_id: None, - multiple_capture_count: None, - surcharge_amount: None, - tax_amount: None, - amount_capturable: None, - merchant_connector_id: None, - unified_code: None, - unified_message: None, - external_three_ds_authentication_attempted: None, - authentication_connector: None, - authentication_id: None, - fingerprint_id: None, - payment_method_billing_address_id: None, - client_source: None, - client_version: None, - customer_acceptance: None, - card_network: None, - shipping_cost: None, - order_tax_amount: None, - }, + } => { + let (connector_transaction_id, connector_transaction_data) = + connector_transaction_id + .map(ConnectorTransactionId::form_id_and_data) + .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) + .unwrap_or((None, None)); + Self { + authentication_data, + encoded_data, + connector_transaction_id, + connector: connector.map(Some), + modified_at: common_utils::date_time::now(), + updated_by, + charge_id, + connector_transaction_data, + amount: None, + net_amount: None, + currency: None, + status: None, + amount_to_capture: None, + authentication_type: None, + payment_method: None, + error_message: None, + payment_method_id: None, + cancellation_reason: None, + mandate_id: None, + browser_info: None, + payment_token: None, + error_code: None, + connector_metadata: None, + payment_method_data: None, + payment_method_type: None, + payment_experience: None, + business_sub_label: None, + straight_through_algorithm: None, + preprocessing_step_id: None, + error_reason: None, + capture_method: None, + connector_response_reference_id: None, + multiple_capture_count: None, + surcharge_amount: None, + tax_amount: None, + amount_capturable: None, + merchant_connector_id: None, + unified_code: None, + unified_message: None, + external_three_ds_authentication_attempted: None, + authentication_connector: None, + authentication_id: None, + fingerprint_id: None, + payment_method_billing_address_id: None, + client_source: None, + client_version: None, + customer_acceptance: None, + card_network: None, + shipping_cost: None, + order_tax_amount: None, + connector_mandate_detail: None, + } + } PaymentAttemptUpdate::IncrementalAuthorizationAmountUpdate { amount, amount_capturable, @@ -3001,6 +3212,8 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_transaction_data: None, + connector_mandate_detail: None, }, PaymentAttemptUpdate::AuthenticationUpdate { status, @@ -3058,6 +3271,8 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_transaction_data: None, + connector_mandate_detail: None, }, PaymentAttemptUpdate::ManualUpdate { status, @@ -3068,19 +3283,82 @@ impl From for PaymentAttemptUpdateInternal { unified_code, unified_message, connector_transaction_id, + } => { + let (connector_transaction_id, connector_transaction_data) = + connector_transaction_id + .map(ConnectorTransactionId::form_id_and_data) + .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) + .unwrap_or((None, None)); + Self { + status, + error_code: error_code.map(Some), + modified_at: common_utils::date_time::now(), + error_message: error_message.map(Some), + error_reason: error_reason.map(Some), + updated_by, + unified_code: unified_code.map(Some), + unified_message: unified_message.map(Some), + connector_transaction_id, + connector_transaction_data, + amount: None, + net_amount: None, + currency: None, + amount_to_capture: None, + connector: None, + authentication_type: None, + payment_method: None, + payment_method_id: None, + cancellation_reason: None, + mandate_id: None, + browser_info: None, + payment_token: None, + connector_metadata: None, + payment_method_data: None, + payment_method_type: None, + payment_experience: None, + business_sub_label: None, + straight_through_algorithm: None, + preprocessing_step_id: None, + capture_method: None, + connector_response_reference_id: None, + multiple_capture_count: None, + surcharge_amount: None, + tax_amount: None, + amount_capturable: None, + merchant_connector_id: None, + authentication_data: None, + encoded_data: None, + external_three_ds_authentication_attempted: None, + authentication_connector: None, + authentication_id: None, + fingerprint_id: None, + payment_method_billing_address_id: None, + charge_id: None, + client_source: None, + client_version: None, + customer_acceptance: None, + card_network: None, + shipping_cost: None, + order_tax_amount: None, + connector_mandate_detail: None, + } + } + PaymentAttemptUpdate::PostSessionTokensUpdate { + updated_by, + connector_metadata, } => Self { - status, - error_code: error_code.map(Some), + status: None, + error_code: None, modified_at: common_utils::date_time::now(), - error_message: error_message.map(Some), - error_reason: error_reason.map(Some), + error_message: None, + error_reason: None, updated_by, - unified_code: unified_code.map(Some), - unified_message: unified_message.map(Some), + unified_code: None, + unified_message: None, amount: None, net_amount: None, currency: None, - connector_transaction_id, + connector_transaction_id: None, amount_to_capture: None, connector: None, authentication_type: None, @@ -3090,7 +3368,7 @@ impl From for PaymentAttemptUpdateInternal { mandate_id: None, browser_info: None, payment_token: None, - connector_metadata: None, + connector_metadata, payment_method_data: None, payment_method_type: None, payment_experience: None, @@ -3118,11 +3396,62 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, + connector_transaction_data: None, + connector_mandate_detail: None, }, } } } +#[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, diesel::AsExpression)] +#[diesel(sql_type = diesel::sql_types::Jsonb)] +pub enum RedirectForm { + Form { + endpoint: String, + method: common_utils::request::Method, + form_fields: std::collections::HashMap, + }, + Html { + html_data: String, + }, + BlueSnap { + payment_fields_token: String, + }, + CybersourceAuthSetup { + access_token: String, + ddc_url: String, + reference_id: String, + }, + CybersourceConsumerAuth { + access_token: String, + step_up_url: String, + }, + Payme, + Braintree { + client_token: String, + card_token: String, + bin: String, + }, + Nmi { + amount: String, + currency: common_enums::Currency, + public_key: masking::Secret, + customer_vault_id: String, + order_id: String, + }, + Mifinity { + initialization_token: String, + }, + WorldpayDDCForm { + endpoint: common_utils::types::Url, + method: common_utils::request::Method, + form_fields: std::collections::HashMap, + collection_id: Option, + }, +} + +common_utils::impl_to_sql_from_sql_json!(RedirectForm); + mod tests { #[test] @@ -3203,7 +3532,6 @@ mod tests { "user_agent": "amet irure esse" } }, - "mandate_type": { "single_use": { "amount": 6540, "currency": "USD" diff --git a/crates/diesel_models/src/payment_intent.rs b/crates/diesel_models/src/payment_intent.rs index 23d1a0f1ac8d..4cb48f6c36e7 100644 --- a/crates/diesel_models/src/payment_intent.rs +++ b/crates/diesel_models/src/payment_intent.rs @@ -4,11 +4,13 @@ use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; -use crate::enums as storage_enums; #[cfg(feature = "v1")] use crate::schema::payment_intent; #[cfg(feature = "v2")] use crate::schema_v2::payment_intent; +#[cfg(feature = "v2")] +use crate::types::{FeatureMetadata, OrderDetailsWithAmount}; +use crate::{business_profile::PaymentLinkBackgroundImageConfig, enums as storage_enums}; #[cfg(feature = "v2")] #[derive(Clone, Debug, PartialEq, Identifiable, Queryable, Serialize, Deserialize, Selectable)] @@ -19,7 +21,7 @@ pub struct PaymentIntent { pub amount: MinorUnit, pub currency: storage_enums::Currency, pub amount_captured: Option, - pub customer_id: Option, + pub customer_id: Option, pub description: Option, pub return_url: Option, pub metadata: Option, @@ -31,12 +33,12 @@ pub struct PaymentIntent { pub last_synced: Option, pub setup_future_usage: Option, pub client_secret: common_utils::types::ClientSecret, - pub active_attempt_id: String, - #[diesel(deserialize_as = super::OptionalDieselArray)] - pub order_details: Option>, + pub active_attempt_id: Option, + #[diesel(deserialize_as = super::OptionalDieselArray>)] + pub order_details: Option>>, pub allowed_payment_method_types: Option, pub connector_metadata: Option, - pub feature_metadata: Option, + pub feature_metadata: Option, pub attempt_count: i16, pub profile_id: common_utils::id_type::ProfileId, pub payment_link_id: Option, @@ -44,7 +46,7 @@ pub struct PaymentIntent { pub surcharge_applicable: Option, pub request_incremental_authorization: Option, pub authorization_count: Option, - pub session_expiry: Option, + pub session_expiry: PrimitiveDateTime, pub request_external_three_ds_authentication: Option, pub frm_metadata: Option, pub customer_details: Option, @@ -52,7 +54,7 @@ pub struct PaymentIntent { pub organization_id: common_utils::id_type::OrganizationId, pub tax_details: Option, pub skip_external_tax_calculation: Option, - pub merchant_reference_id: Option, + pub merchant_reference_id: Option, pub billing_address: Option, pub shipping_address: Option, pub capture_method: Option, @@ -70,6 +72,9 @@ pub struct PaymentIntent { pub routing_algorithm_id: Option, pub payment_link_config: Option, pub id: common_utils::id_type::GlobalPaymentId, + pub psd2_sca_exemption_type: Option, + pub split_payments: Option, + pub platform_merchant_id: Option, } #[cfg(feature = "v1")] @@ -134,9 +139,13 @@ pub struct PaymentIntent { pub organization_id: common_utils::id_type::OrganizationId, pub tax_details: Option, pub skip_external_tax_calculation: Option, + pub psd2_sca_exemption_type: Option, + pub split_payments: Option, + pub platform_merchant_id: Option, } -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq)] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, diesel::AsExpression, PartialEq)] +#[diesel(sql_type = diesel::sql_types::Jsonb)] pub struct PaymentLinkConfigRequestForPayments { /// custom theme for the payment link pub theme: Option, @@ -150,8 +159,18 @@ pub struct PaymentLinkConfigRequestForPayments { pub display_sdk_only: Option, /// Enable saved payment method option for payment link pub enabled_saved_payment_method: Option, + /// Hide card nickname field option for payment link + pub hide_card_nickname_field: Option, + /// Show card form by default for payment link + pub show_card_form_by_default: Option, /// Dynamic details related to merchant to be rendered in payment link pub transaction_details: Option>, + /// Configurations for the background image for details section + pub background_image: Option, + /// Custom layout for details section + pub details_layout: Option, + /// Text for payment link's handle confirm button + pub payment_button_text: Option, } common_utils::impl_to_sql_from_sql_json!(PaymentLinkConfigRequestForPayments); @@ -192,6 +211,26 @@ pub struct TaxDetails { pub payment_method_type: Option, } +impl TaxDetails { + /// Get the tax amount + /// If default tax is present, return the default tax amount + /// If default tax is not present, return the tax amount based on the payment method if it matches the provided payment method type + pub fn get_tax_amount(&self, payment_method: PaymentMethodType) -> Option { + self.payment_method_type + .as_ref() + .filter(|payment_method_type_tax| payment_method_type_tax.pmt == payment_method) + .map(|payment_method_type_tax| payment_method_type_tax.order_tax_amount) + .or_else(|| self.get_default_tax_amount()) + } + + /// Get the default tax amount + pub fn get_default_tax_amount(&self) -> Option { + self.default + .as_ref() + .map(|default_tax_details| default_tax_details.order_tax_amount) + } +} + common_utils::impl_to_sql_from_sql_json!(TaxDetails); #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -216,7 +255,7 @@ pub struct PaymentIntentNew { pub amount: MinorUnit, pub currency: storage_enums::Currency, pub amount_captured: Option, - pub customer_id: Option, + pub customer_id: Option, pub description: Option, pub return_url: Option, pub metadata: Option, @@ -228,12 +267,12 @@ pub struct PaymentIntentNew { pub last_synced: Option, pub setup_future_usage: Option, pub client_secret: common_utils::types::ClientSecret, - pub active_attempt_id: String, - #[diesel(deserialize_as = super::OptionalDieselArray)] - pub order_details: Option>, + pub active_attempt_id: Option, + #[diesel(deserialize_as = super::OptionalDieselArray>)] + pub order_details: Option>>, pub allowed_payment_method_types: Option, pub connector_metadata: Option, - pub feature_metadata: Option, + pub feature_metadata: Option, pub attempt_count: i16, pub profile_id: common_utils::id_type::ProfileId, pub payment_link_id: Option, @@ -241,8 +280,8 @@ pub struct PaymentIntentNew { pub surcharge_applicable: Option, pub request_incremental_authorization: Option, pub authorization_count: Option, - #[serde(with = "common_utils::custom_serde::iso8601::option")] - pub session_expiry: Option, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub session_expiry: PrimitiveDateTime, pub request_external_three_ds_authentication: Option, pub frm_metadata: Option, pub customer_details: Option, @@ -250,7 +289,7 @@ pub struct PaymentIntentNew { pub organization_id: common_utils::id_type::OrganizationId, pub tax_details: Option, pub skip_external_tax_calculation: Option, - pub merchant_reference_id: Option, + pub merchant_reference_id: Option, pub billing_address: Option, pub shipping_address: Option, pub capture_method: Option, @@ -263,6 +302,7 @@ pub struct PaymentIntentNew { pub enable_payment_link: Option, pub apply_mit_exemption: Option, pub id: common_utils::id_type::GlobalPaymentId, + pub platform_merchant_id: Option, } #[cfg(feature = "v1")] @@ -328,83 +368,24 @@ pub struct PaymentIntentNew { pub organization_id: common_utils::id_type::OrganizationId, pub tax_details: Option, pub skip_external_tax_calculation: Option, + pub psd2_sca_exemption_type: Option, + pub platform_merchant_id: Option, + pub split_payments: Option, } #[cfg(feature = "v2")] #[derive(Debug, Clone, Serialize, Deserialize)] pub enum PaymentIntentUpdate { - ResponseUpdate { + /// Update the payment intent details on payment intent confirmation, before calling the connector + ConfirmIntent { status: storage_enums::IntentStatus, - amount_captured: Option, - // Moved to attempt - // fingerprint_id: Option, - return_url: Option, - updated_by: String, - // Moved to attempt - // incremental_authorization_allowed: Option, - }, - MetadataUpdate { - metadata: pii::SecretSerdeValue, - updated_by: String, - }, - Update(Box), - PaymentCreateUpdate { - return_url: Option, - status: Option, - customer_id: Option, - shipping_address: Option, - billing_address: Option, - customer_details: Option, + active_attempt_id: common_utils::id_type::GlobalAttemptId, updated_by: String, }, - MerchantStatusUpdate { - status: storage_enums::IntentStatus, - shipping_address: Option, - billing_address: Option, - updated_by: String, - }, - PGStatusUpdate { + /// Update the payment intent details on payment intent confirmation, after calling the connector + ConfirmIntentPostUpdate { status: storage_enums::IntentStatus, - updated_by: String, - // Moved to attempt - // incremental_authorization_allowed: Option, - }, - PaymentAttemptAndAttemptCountUpdate { - active_attempt_id: String, - attempt_count: i16, - updated_by: String, - }, - StatusAndAttemptUpdate { - status: storage_enums::IntentStatus, - active_attempt_id: String, - attempt_count: i16, - updated_by: String, - }, - ApproveUpdate { - status: storage_enums::IntentStatus, - frm_merchant_decision: Option, - updated_by: String, - }, - RejectUpdate { - status: storage_enums::IntentStatus, - frm_merchant_decision: Option, - updated_by: String, - }, - SurchargeApplicableUpdate { - surcharge_applicable: Option, - updated_by: String, - }, - IncrementalAuthorizationAmountUpdate { - amount: MinorUnit, - }, - AuthorizationCountUpdate { - authorization_count: i32, - }, - CompleteAuthorizeUpdate { - shipping_address: Option, - }, - ManualUpdate { - status: Option, + amount_captured: Option, updated_by: String, }, } @@ -416,7 +397,6 @@ pub enum PaymentIntentUpdate { status: storage_enums::IntentStatus, amount_captured: Option, fingerprint_id: Option, - return_url: Option, updated_by: String, incremental_authorization_allowed: Option, }, @@ -548,35 +528,45 @@ pub struct PaymentIntentUpdateFields { pub tax_details: Option, } +// TODO: uncomment fields as necessary #[cfg(feature = "v2")] -#[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)] +#[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay, Serialize, Deserialize)] #[diesel(table_name = payment_intent)] pub struct PaymentIntentUpdateInternal { - pub amount: Option, - pub currency: Option, pub status: Option, pub amount_captured: Option, - pub customer_id: Option, - pub return_url: Option, - pub setup_future_usage: Option, - pub metadata: Option, pub modified_at: PrimitiveDateTime, - pub active_attempt_id: Option, - pub description: Option, - pub statement_descriptor: Option, - #[diesel(deserialize_as = super::OptionalDieselArray)] - pub order_details: Option>, - pub attempt_count: Option, - pub updated_by: String, + pub active_attempt_id: Option, + pub amount: Option, + pub currency: Option, + pub shipping_cost: Option, + pub tax_details: Option, + pub skip_external_tax_calculation: Option, pub surcharge_applicable: Option, - pub authorization_count: Option, - pub session_expiry: Option, - pub request_external_three_ds_authentication: Option, - pub frm_metadata: Option, - pub customer_details: Option, + pub surcharge_amount: Option, + pub tax_on_surcharge: Option, + pub routing_algorithm_id: Option, + pub capture_method: Option, + pub authentication_type: Option, pub billing_address: Option, pub shipping_address: Option, - pub frm_merchant_decision: Option, + pub customer_present: Option, + pub description: Option, + pub return_url: Option, + pub setup_future_usage: Option, + pub apply_mit_exemption: Option, + pub statement_descriptor: Option, + pub order_details: Option>>, + pub allowed_payment_method_types: Option, + pub metadata: Option, + pub connector_metadata: Option, + pub feature_metadata: Option, + pub payment_link_config: Option, + pub request_incremental_authorization: Option, + pub session_expiry: Option, + pub frm_metadata: Option, + pub request_external_three_ds_authentication: Option, + pub updated_by: String, } #[cfg(feature = "v1")] @@ -622,77 +612,6 @@ pub struct PaymentIntentUpdateInternal { pub tax_details: Option, } -#[cfg(feature = "v2")] -impl PaymentIntentUpdate { - pub fn apply_changeset(self, source: PaymentIntent) -> PaymentIntent { - todo!() - // let PaymentIntentUpdateInternal { - // amount, - // currency, - // status, - // amount_captured, - // customer_id, - // return_url, - // setup_future_usage, - // off_session, - // metadata, - // modified_at: _, - // active_attempt_id, - // description, - // statement_descriptor, - // order_details, - // attempt_count, - // frm_merchant_decision, - // payment_confirm_source, - // updated_by, - // surcharge_applicable, - // authorization_count, - // session_expiry, - // request_external_three_ds_authentication, - // frm_metadata, - // customer_details, - // billing_address, - // merchant_order_reference_id, - // shipping_address, - // is_payment_processor_token_flow, - // } = self.into(); - // PaymentIntent { - // amount: amount.unwrap_or(source.amount), - // currency: currency.unwrap_or(source.currency), - // status: status.unwrap_or(source.status), - // amount_captured: amount_captured.or(source.amount_captured), - // customer_id: customer_id.or(source.customer_id), - // return_url: return_url.or(source.return_url), - // setup_future_usage: setup_future_usage.or(source.setup_future_usage), - // off_session: off_session.or(source.off_session), - // metadata: metadata.or(source.metadata), - // modified_at: common_utils::date_time::now(), - // active_attempt_id: active_attempt_id.unwrap_or(source.active_attempt_id), - // description: description.or(source.description), - // statement_descriptor: statement_descriptor.or(source.statement_descriptor), - // order_details: order_details.or(source.order_details), - // attempt_count: attempt_count.unwrap_or(source.attempt_count), - // frm_merchant_decision: frm_merchant_decision.or(source.frm_merchant_decision), - // payment_confirm_source: payment_confirm_source.or(source.payment_confirm_source), - // updated_by, - // surcharge_applicable: surcharge_applicable.or(source.surcharge_applicable), - // authorization_count: authorization_count.or(source.authorization_count), - // session_expiry: session_expiry.or(source.session_expiry), - // request_external_three_ds_authentication: request_external_three_ds_authentication - // .or(source.request_external_three_ds_authentication), - // frm_metadata: frm_metadata.or(source.frm_metadata), - // customer_details: customer_details.or(source.customer_details), - // billing_address: billing_address.or(source.billing_address), - // shipping_address: shipping_address.or(source.shipping_address), - // merchant_order_reference_id: merchant_order_reference_id - // .or(source.merchant_order_reference_id), - // is_payment_processor_token_flow: is_payment_processor_token_flow - // .or(source.is_payment_processor_token_flow), - // ..source - // } - } -} - #[cfg(feature = "v1")] impl PaymentIntentUpdate { pub fn apply_changeset(self, source: PaymentIntent) -> PaymentIntent { @@ -783,515 +702,6 @@ impl PaymentIntentUpdate { } } -#[cfg(feature = "v2")] -impl From for PaymentIntentUpdateInternal { - fn from(payment_intent_update: PaymentIntentUpdate) -> Self { - todo!() - // match payment_intent_update { - // PaymentIntentUpdate::MetadataUpdate { - // metadata, - // updated_by, - // } => Self { - // metadata: Some(metadata), - // modified_at: common_utils::date_time::now(), - // updated_by, - // amount: None, - // currency: None, - // status: None, - // amount_captured: None, - // customer_id: None, - // return_url: None, - // setup_future_usage: None, - // off_session: None, - // active_attempt_id: None, - // description: None, - // statement_descriptor: None, - // order_details: None, - // attempt_count: None, - // frm_merchant_decision: None, - // payment_confirm_source: None, - // surcharge_applicable: None, - // authorization_count: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // customer_details: None, - // billing_address: None, - // merchant_order_reference_id: None, - // shipping_address: None, - // is_payment_processor_token_flow: None, - // }, - // PaymentIntentUpdate::Update(value) => Self { - // amount: Some(value.amount), - // currency: Some(value.currency), - // setup_future_usage: value.setup_future_usage, - // status: Some(value.status), - // customer_id: value.customer_id, - // shipping_address: value.shipping_address, - // billing_address: value.billing_address, - // return_url: value.return_url, - // description: value.description, - // statement_descriptor: value.statement_descriptor, - // order_details: value.order_details, - // metadata: value.metadata, - // payment_confirm_source: value.payment_confirm_source, - // updated_by: value.updated_by, - // session_expiry: value.session_expiry, - // request_external_three_ds_authentication: value - // .request_external_three_ds_authentication, - // frm_metadata: value.frm_metadata, - // customer_details: value.customer_details, - // merchant_order_reference_id: value.merchant_order_reference_id, - // amount_captured: None, - // off_session: None, - // modified_at: common_utils::date_time::now(), - // active_attempt_id: None, - // attempt_count: None, - // frm_merchant_decision: None, - // surcharge_applicable: None, - // authorization_count: None, - // is_payment_processor_token_flow: value.is_payment_processor_token_flow, - // }, - // PaymentIntentUpdate::PaymentCreateUpdate { - // return_url, - // status, - // customer_id, - // shipping_address, - // billing_address, - // customer_details, - // updated_by, - // } => Self { - // return_url, - // status, - // customer_id, - // customer_details, - // modified_at: common_utils::date_time::now(), - // updated_by, - // amount: None, - // currency: None, - // amount_captured: None, - // setup_future_usage: None, - // off_session: None, - // metadata: None, - // active_attempt_id: None, - // description: None, - // statement_descriptor: None, - // order_details: None, - // attempt_count: None, - // frm_merchant_decision: None, - // payment_confirm_source: None, - // surcharge_applicable: None, - // authorization_count: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // billing_address, - // merchant_order_reference_id: None, - // shipping_address, - // is_payment_processor_token_flow: None, - // }, - // PaymentIntentUpdate::PGStatusUpdate { status, updated_by } => Self { - // status: Some(status), - // modified_at: common_utils::date_time::now(), - // updated_by, - // amount: None, - // currency: None, - // amount_captured: None, - // customer_id: None, - // return_url: None, - // setup_future_usage: None, - // off_session: None, - // metadata: None, - // active_attempt_id: None, - // description: None, - // statement_descriptor: None, - // order_details: None, - // attempt_count: None, - // frm_merchant_decision: None, - // payment_confirm_source: None, - // surcharge_applicable: None, - // authorization_count: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // customer_details: None, - // billing_address: None, - // merchant_order_reference_id: None, - // shipping_address: None, - // is_payment_processor_token_flow: None, - // }, - // PaymentIntentUpdate::MerchantStatusUpdate { - // status, - // billing_address, - // shipping_address, - // updated_by, - // } => Self { - // status: Some(status), - // modified_at: common_utils::date_time::now(), - // updated_by, - // amount: None, - // currency: None, - // amount_captured: None, - // customer_id: None, - // return_url: None, - // setup_future_usage: None, - // off_session: None, - // metadata: None, - // active_attempt_id: None, - // description: None, - // statement_descriptor: None, - // order_details: None, - // attempt_count: None, - // frm_merchant_decision: None, - // payment_confirm_source: None, - // surcharge_applicable: None, - // authorization_count: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // customer_details: None, - // billing_address, - // merchant_order_reference_id: None, - // shipping_address, - // is_payment_processor_token_flow: None, - // }, - // PaymentIntentUpdate::ResponseUpdate { - // // amount, - // // currency, - // status, - // amount_captured, - // // customer_id, - // return_url, - // updated_by, - // } => Self { - // // amount, - // // currency: Some(currency), - // status: Some(status), - // amount_captured, - // // customer_id, - // return_url, - // modified_at: common_utils::date_time::now(), - // updated_by, - // amount: None, - // currency: None, - // customer_id: None, - // setup_future_usage: None, - // off_session: None, - // metadata: None, - // active_attempt_id: None, - // description: None, - // statement_descriptor: None, - // order_details: None, - // attempt_count: None, - // frm_merchant_decision: None, - // payment_confirm_source: None, - // surcharge_applicable: None, - // authorization_count: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // customer_details: None, - // billing_address: None, - // merchant_order_reference_id: None, - // shipping_address: None, - // is_payment_processor_token_flow: None, - // }, - // PaymentIntentUpdate::PaymentAttemptAndAttemptCountUpdate { - // active_attempt_id, - // attempt_count, - // updated_by, - // } => Self { - // active_attempt_id: Some(active_attempt_id), - // attempt_count: Some(attempt_count), - // updated_by, - // amount: None, - // currency: None, - // status: None, - // amount_captured: None, - // customer_id: None, - // return_url: None, - // setup_future_usage: None, - // off_session: None, - // metadata: None, - // modified_at: common_utils::date_time::now(), - // description: None, - // statement_descriptor: None, - // order_details: None, - // frm_merchant_decision: None, - // payment_confirm_source: None, - // surcharge_applicable: None, - // authorization_count: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // customer_details: None, - // billing_address: None, - // merchant_order_reference_id: None, - // shipping_address: None, - // is_payment_processor_token_flow: None, - // }, - // PaymentIntentUpdate::StatusAndAttemptUpdate { - // status, - // active_attempt_id, - // attempt_count, - // updated_by, - // } => Self { - // status: Some(status), - // active_attempt_id: Some(active_attempt_id), - // attempt_count: Some(attempt_count), - // updated_by, - // amount: None, - // currency: None, - // amount_captured: None, - // customer_id: None, - // return_url: None, - // setup_future_usage: None, - // off_session: None, - // metadata: None, - // modified_at: common_utils::date_time::now(), - // description: None, - // statement_descriptor: None, - // order_details: None, - // frm_merchant_decision: None, - // payment_confirm_source: None, - // surcharge_applicable: None, - // authorization_count: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // customer_details: None, - // billing_address: None, - // merchant_order_reference_id: None, - // shipping_address: None, - // is_payment_processor_token_flow: None, - // }, - // PaymentIntentUpdate::ApproveUpdate { - // status, - // frm_merchant_decision, - // updated_by, - // } => Self { - // status: Some(status), - // frm_merchant_decision, - // updated_by, - // amount: None, - // currency: None, - // amount_captured: None, - // customer_id: None, - // return_url: None, - // setup_future_usage: None, - // off_session: None, - // metadata: None, - // modified_at: common_utils::date_time::now(), - // active_attempt_id: None, - // description: None, - // statement_descriptor: None, - // order_details: None, - // attempt_count: None, - // payment_confirm_source: None, - // surcharge_applicable: None, - // authorization_count: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // customer_details: None, - // billing_address: None, - // merchant_order_reference_id: None, - // shipping_address: None, - // is_payment_processor_token_flow: None, - // }, - // PaymentIntentUpdate::RejectUpdate { - // status, - // frm_merchant_decision, - // updated_by, - // } => Self { - // status: Some(status), - // frm_merchant_decision, - // updated_by, - // amount: None, - // currency: None, - // amount_captured: None, - // customer_id: None, - // return_url: None, - // setup_future_usage: None, - // off_session: None, - // metadata: None, - // modified_at: common_utils::date_time::now(), - // active_attempt_id: None, - // description: None, - // statement_descriptor: None, - // order_details: None, - // attempt_count: None, - // payment_confirm_source: None, - // surcharge_applicable: None, - // authorization_count: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // customer_details: None, - // billing_address: None, - // merchant_order_reference_id: None, - // shipping_address: None, - // is_payment_processor_token_flow: None, - // }, - // PaymentIntentUpdate::SurchargeApplicableUpdate { - // surcharge_applicable, - // updated_by, - // } => Self { - // surcharge_applicable, - // updated_by, - // amount: None, - // currency: None, - // status: None, - // amount_captured: None, - // customer_id: None, - // return_url: None, - // setup_future_usage: None, - // off_session: None, - // metadata: None, - // modified_at: common_utils::date_time::now(), - // active_attempt_id: None, - // description: None, - // statement_descriptor: None, - // order_details: None, - // attempt_count: None, - // frm_merchant_decision: None, - // payment_confirm_source: None, - // authorization_count: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // customer_details: None, - // billing_address: None, - // merchant_order_reference_id: None, - // shipping_address: None, - // is_payment_processor_token_flow: None, - // }, - // PaymentIntentUpdate::IncrementalAuthorizationAmountUpdate { amount } => Self { - // amount: Some(amount), - // currency: None, - // status: None, - // amount_captured: None, - // customer_id: None, - // return_url: None, - // setup_future_usage: None, - // off_session: None, - // metadata: None, - // modified_at: common_utils::date_time::now(), - // active_attempt_id: None, - // description: None, - // statement_descriptor: None, - // order_details: None, - // attempt_count: None, - // frm_merchant_decision: None, - // payment_confirm_source: None, - // updated_by: String::default(), - // surcharge_applicable: None, - // authorization_count: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // customer_details: None, - // billing_address: None, - // merchant_order_reference_id: None, - // shipping_address: None, - // is_payment_processor_token_flow: None, - // }, - // PaymentIntentUpdate::AuthorizationCountUpdate { - // authorization_count, - // } => Self { - // authorization_count: Some(authorization_count), - // amount: None, - // currency: None, - // status: None, - // amount_captured: None, - // customer_id: None, - // return_url: None, - // setup_future_usage: None, - // off_session: None, - // metadata: None, - // modified_at: common_utils::date_time::now(), - // active_attempt_id: None, - // description: None, - // statement_descriptor: None, - // order_details: None, - // attempt_count: None, - // frm_merchant_decision: None, - // payment_confirm_source: None, - // updated_by: String::default(), - // surcharge_applicable: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // customer_details: None, - // billing_address: None, - // merchant_order_reference_id: None, - // shipping_address: None, - // is_payment_processor_token_flow: None, - // }, - // PaymentIntentUpdate::CompleteAuthorizeUpdate { shipping_address } => Self { - // amount: None, - // currency: None, - // status: None, - // amount_captured: None, - // customer_id: None, - // return_url: None, - // setup_future_usage: None, - // off_session: None, - // metadata: None, - // modified_at: common_utils::date_time::now(), - // active_attempt_id: None, - // description: None, - // statement_descriptor: None, - // order_details: None, - // attempt_count: None, - // frm_merchant_decision: None, - // payment_confirm_source: None, - // updated_by: String::default(), - // surcharge_applicable: None, - // authorization_count: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // customer_details: None, - // billing_address: None, - // merchant_order_reference_id: None, - // shipping_address, - // is_payment_processor_token_flow: None, - // }, - // PaymentIntentUpdate::ManualUpdate { status, updated_by } => Self { - // status, - // updated_by, - // amount: None, - // currency: None, - // amount_captured: None, - // customer_id: None, - // return_url: None, - // setup_future_usage: None, - // off_session: None, - // metadata: None, - // modified_at: common_utils::date_time::now(), - // active_attempt_id: None, - // description: None, - // statement_descriptor: None, - // order_details: None, - // attempt_count: None, - // frm_merchant_decision: None, - // payment_confirm_source: None, - // surcharge_applicable: None, - // authorization_count: None, - // session_expiry: None, - // request_external_three_ds_authentication: None, - // frm_metadata: None, - // customer_details: None, - // billing_address: None, - // merchant_order_reference_id: None, - // shipping_address: None, - // is_payment_processor_token_flow: None, - // }, - // } - } -} - #[cfg(feature = "v1")] impl From for PaymentIntentUpdateInternal { fn from(payment_intent_update: PaymentIntentUpdate) -> Self { @@ -1514,7 +924,6 @@ impl From for PaymentIntentUpdateInternal { amount_captured, fingerprint_id, // customer_id, - return_url, updated_by, incremental_authorization_allowed, } => Self { @@ -1524,7 +933,7 @@ impl From for PaymentIntentUpdateInternal { amount_captured, fingerprint_id, // customer_id, - return_url, + return_url: None, modified_at: common_utils::date_time::now(), updated_by, incremental_authorization_allowed, diff --git a/crates/diesel_models/src/payment_method.rs b/crates/diesel_models/src/payment_method.rs index 3ad58e62dcf9..76613984363d 100644 --- a/crates/diesel_models/src/payment_method.rs +++ b/crates/diesel_models/src/payment_method.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use common_enums::MerchantStorageScheme; use common_utils::{encryption::Encryption, pii}; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; @@ -5,7 +7,7 @@ use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; any(feature = "v1", feature = "v2"), not(feature = "payment_methods_v2") ))] -use masking::Secret; +use masking::{ExposeInterface, Secret}; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -65,22 +67,17 @@ pub struct PaymentMethod { } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -#[derive( - Clone, Debug, Eq, PartialEq, Identifiable, Queryable, Selectable, Serialize, Deserialize, -)] +#[derive(Clone, Debug, Identifiable, Queryable, Selectable, Serialize, Deserialize)] #[diesel(table_name = payment_methods, primary_key(id), check_for_backend(diesel::pg::Pg))] pub struct PaymentMethod { - pub customer_id: common_utils::id_type::CustomerId, + pub customer_id: common_utils::id_type::GlobalCustomerId, pub merchant_id: common_utils::id_type::MerchantId, pub created_at: PrimitiveDateTime, pub last_modified: PrimitiveDateTime, - pub payment_method: Option, - pub payment_method_type: Option, - pub metadata: Option, pub payment_method_data: Option, pub locker_id: Option, pub last_used_at: PrimitiveDateTime, - pub connector_mandate_details: Option, + pub connector_mandate_details: Option, pub customer_acceptance: Option, pub status: storage_enums::PaymentMethodStatus, pub network_transaction_id: Option, @@ -88,6 +85,8 @@ pub struct PaymentMethod { pub payment_method_billing_address: Option, pub updated_by: Option, pub locker_fingerprint_id: Option, + pub payment_method_type_v2: Option, + pub payment_method_subtype: Option, pub id: common_utils::id_type::GlobalPaymentMethodId, pub version: common_enums::ApiVersion, pub network_token_requestor_reference_id: Option, @@ -156,22 +155,17 @@ pub struct PaymentMethodNew { } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -#[derive( - Clone, Debug, Eq, PartialEq, Insertable, router_derive::DebugAsDisplay, Serialize, Deserialize, -)] +#[derive(Clone, Debug, Insertable, router_derive::DebugAsDisplay, Serialize, Deserialize)] #[diesel(table_name = payment_methods)] pub struct PaymentMethodNew { - pub customer_id: common_utils::id_type::CustomerId, + pub customer_id: common_utils::id_type::GlobalCustomerId, pub merchant_id: common_utils::id_type::MerchantId, - pub payment_method: Option, - pub payment_method_type: Option, pub created_at: PrimitiveDateTime, pub last_modified: PrimitiveDateTime, - pub metadata: Option, pub payment_method_data: Option, pub locker_id: Option, pub last_used_at: PrimitiveDateTime, - pub connector_mandate_details: Option, + pub connector_mandate_details: Option, pub customer_acceptance: Option, pub status: storage_enums::PaymentMethodStatus, pub network_transaction_id: Option, @@ -179,6 +173,8 @@ pub struct PaymentMethodNew { pub payment_method_billing_address: Option, pub updated_by: Option, pub locker_fingerprint_id: Option, + pub payment_method_type_v2: Option, + pub payment_method_subtype: Option, pub id: common_utils::id_type::GlobalPaymentMethodId, pub version: common_enums::ApiVersion, pub network_token_requestor_reference_id: Option, @@ -223,6 +219,7 @@ pub enum PaymentMethodUpdate { }, UpdatePaymentMethodDataAndLastUsed { payment_method_data: Option, + scheme: Option, last_used_at: PrimitiveDateTime, }, PaymentMethodDataUpdate { @@ -252,17 +249,23 @@ pub enum PaymentMethodUpdate { ConnectorMandateDetailsUpdate { connector_mandate_details: Option, }, + NetworkTokenDataUpdate { + network_token_requestor_reference_id: Option, + network_token_locker_id: Option, + network_token_payment_method_data: Option, + }, + ConnectorNetworkTransactionIdAndMandateDetailsUpdate { + connector_mandate_details: Option, + network_transaction_id: Option>, + }, } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[derive(Debug, Serialize, Deserialize)] pub enum PaymentMethodUpdate { - MetadataUpdateAndLastUsed { - metadata: Option, - last_used_at: PrimitiveDateTime, - }, UpdatePaymentMethodDataAndLastUsed { payment_method_data: Option, + scheme: Option, last_used_at: PrimitiveDateTime, }, PaymentMethodDataUpdate { @@ -282,14 +285,15 @@ pub enum PaymentMethodUpdate { payment_method_data: Option, status: Option, locker_id: Option, - payment_method: Option, - payment_method_type: Option, + payment_method_type_v2: Option, + payment_method_subtype: Option, network_token_requestor_reference_id: Option, network_token_locker_id: Option, network_token_payment_method_data: Option, + locker_fingerprint_id: Option, }, ConnectorMandateDetailsUpdate { - connector_mandate_details: Option, + connector_mandate_details: Option, }, } @@ -308,54 +312,68 @@ impl PaymentMethodUpdate { #[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay, Serialize, Deserialize)] #[diesel(table_name = payment_methods)] pub struct PaymentMethodUpdateInternal { - metadata: Option, payment_method_data: Option, last_used_at: Option, network_transaction_id: Option, status: Option, locker_id: Option, - payment_method: Option, - connector_mandate_details: Option, + payment_method_type_v2: Option, + connector_mandate_details: Option, updated_by: Option, - payment_method_type: Option, + payment_method_subtype: Option, last_modified: PrimitiveDateTime, network_token_requestor_reference_id: Option, network_token_locker_id: Option, network_token_payment_method_data: Option, + locker_fingerprint_id: Option, } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] impl PaymentMethodUpdateInternal { - pub fn create_payment_method(self, source: PaymentMethod) -> PaymentMethod { - let metadata = self.metadata; - - PaymentMethod { metadata, ..source } - } - pub fn apply_changeset(self, source: PaymentMethod) -> PaymentMethod { let Self { - metadata, payment_method_data, last_used_at, network_transaction_id, status, + locker_id, + payment_method_type_v2, connector_mandate_details, updated_by, - .. + payment_method_subtype, + last_modified, + network_token_requestor_reference_id, + network_token_locker_id, + network_token_payment_method_data, + locker_fingerprint_id, } = self; PaymentMethod { - metadata: metadata.map_or(source.metadata, Some), - payment_method_data: payment_method_data.map_or(source.payment_method_data, Some), + customer_id: source.customer_id, + merchant_id: source.merchant_id, + created_at: source.created_at, + last_modified, + payment_method_data: payment_method_data.or(source.payment_method_data), + locker_id: locker_id.or(source.locker_id), last_used_at: last_used_at.unwrap_or(source.last_used_at), - network_transaction_id: network_transaction_id - .map_or(source.network_transaction_id, Some), - 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), - last_modified: common_utils::date_time::now(), - ..source + .or(source.connector_mandate_details), + customer_acceptance: source.customer_acceptance, + status: status.unwrap_or(source.status), + network_transaction_id: network_transaction_id.or(source.network_transaction_id), + client_secret: source.client_secret, + payment_method_billing_address: source.payment_method_billing_address, + updated_by: updated_by.or(source.updated_by), + locker_fingerprint_id: locker_fingerprint_id.or(source.locker_fingerprint_id), + payment_method_type_v2: payment_method_type_v2.or(source.payment_method_type_v2), + payment_method_subtype: payment_method_subtype.or(source.payment_method_subtype), + id: source.id, + version: source.version, + network_token_requestor_reference_id: network_token_requestor_reference_id + .or(source.network_token_requestor_reference_id), + network_token_locker_id: network_token_locker_id.or(source.network_token_locker_id), + network_token_payment_method_data: network_token_payment_method_data + .or(source.network_token_payment_method_data), } } } @@ -382,6 +400,7 @@ pub struct PaymentMethodUpdateInternal { last_modified: PrimitiveDateTime, network_token_locker_id: Option, network_token_payment_method_data: Option, + scheme: Option, } #[cfg(all( @@ -389,12 +408,6 @@ pub struct PaymentMethodUpdateInternal { not(feature = "payment_methods_v2") ))] impl PaymentMethodUpdateInternal { - pub fn create_payment_method(self, source: PaymentMethod) -> PaymentMethod { - let metadata = self.metadata.map(Secret::new); - - PaymentMethod { metadata, ..source } - } - pub fn apply_changeset(self, source: PaymentMethod) -> PaymentMethod { let Self { metadata, @@ -402,23 +415,57 @@ impl PaymentMethodUpdateInternal { last_used_at, network_transaction_id, status, + locker_id, + network_token_requestor_reference_id, + payment_method, connector_mandate_details, updated_by, - .. + payment_method_type, + payment_method_issuer, + last_modified, + network_token_locker_id, + network_token_payment_method_data, + scheme, } = self; PaymentMethod { + customer_id: source.customer_id, + merchant_id: source.merchant_id, + payment_method_id: source.payment_method_id, + accepted_currency: source.accepted_currency, + scheme: scheme.or(source.scheme), + token: source.token, + cardholder_name: source.cardholder_name, + issuer_name: source.issuer_name, + issuer_country: source.issuer_country, + payer_country: source.payer_country, + is_stored: source.is_stored, + swift_code: source.swift_code, + direct_debit_token: source.direct_debit_token, + created_at: source.created_at, + last_modified, + payment_method: payment_method.or(source.payment_method), + payment_method_type: payment_method_type.or(source.payment_method_type), + payment_method_issuer: payment_method_issuer.or(source.payment_method_issuer), + payment_method_issuer_code: source.payment_method_issuer_code, metadata: metadata.map_or(source.metadata, |v| Some(v.into())), - payment_method_data: payment_method_data.map_or(source.payment_method_data, Some), + payment_method_data: payment_method_data.or(source.payment_method_data), + locker_id: locker_id.or(source.locker_id), last_used_at: last_used_at.unwrap_or(source.last_used_at), - network_transaction_id: network_transaction_id - .map_or(source.network_transaction_id, Some), - 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), - last_modified: common_utils::date_time::now(), - ..source + .or(source.connector_mandate_details), + customer_acceptance: source.customer_acceptance, + status: status.unwrap_or(source.status), + network_transaction_id: network_transaction_id.or(source.network_transaction_id), + client_secret: source.client_secret, + payment_method_billing_address: source.payment_method_billing_address, + updated_by: updated_by.or(source.updated_by), + version: source.version, + network_token_requestor_reference_id: network_token_requestor_reference_id + .or(source.network_token_requestor_reference_id), + network_token_locker_id: network_token_locker_id.or(source.network_token_locker_id), + network_token_payment_method_data: network_token_payment_method_data + .or(source.network_token_payment_method_data), } } } @@ -449,6 +496,7 @@ impl From for PaymentMethodUpdateInternal { last_modified: common_utils::date_time::now(), network_token_locker_id: None, network_token_payment_method_data: None, + scheme: None, }, PaymentMethodUpdate::PaymentMethodDataUpdate { payment_method_data, @@ -468,6 +516,7 @@ impl From for PaymentMethodUpdateInternal { last_modified: common_utils::date_time::now(), network_token_locker_id: None, network_token_payment_method_data: None, + scheme: None, }, PaymentMethodUpdate::LastUsedUpdate { last_used_at } => Self { metadata: None, @@ -485,9 +534,11 @@ impl From for PaymentMethodUpdateInternal { last_modified: common_utils::date_time::now(), network_token_locker_id: None, network_token_payment_method_data: None, + scheme: None, }, PaymentMethodUpdate::UpdatePaymentMethodDataAndLastUsed { payment_method_data, + scheme, last_used_at, } => Self { metadata: None, @@ -505,6 +556,7 @@ impl From for PaymentMethodUpdateInternal { last_modified: common_utils::date_time::now(), network_token_locker_id: None, network_token_payment_method_data: None, + scheme, }, PaymentMethodUpdate::NetworkTransactionIdAndStatusUpdate { network_transaction_id, @@ -525,6 +577,7 @@ impl From for PaymentMethodUpdateInternal { last_modified: common_utils::date_time::now(), network_token_locker_id: None, network_token_payment_method_data: None, + scheme: None, }, PaymentMethodUpdate::StatusUpdate { status } => Self { metadata: None, @@ -542,6 +595,7 @@ impl From for PaymentMethodUpdateInternal { last_modified: common_utils::date_time::now(), network_token_locker_id: None, network_token_payment_method_data: None, + scheme: None, }, PaymentMethodUpdate::AdditionalDataUpdate { payment_method_data, @@ -569,6 +623,7 @@ impl From for PaymentMethodUpdateInternal { last_modified: common_utils::date_time::now(), network_token_locker_id, network_token_payment_method_data, + scheme: None, }, PaymentMethodUpdate::ConnectorMandateDetailsUpdate { connector_mandate_details, @@ -588,164 +643,192 @@ impl From for PaymentMethodUpdateInternal { last_modified: common_utils::date_time::now(), network_token_locker_id: None, network_token_payment_method_data: None, + scheme: None, }, - } - } -} - -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -impl From for PaymentMethodUpdateInternal { - fn from(payment_method_update: PaymentMethodUpdate) -> Self { - match payment_method_update { - PaymentMethodUpdate::MetadataUpdateAndLastUsed { - metadata, - last_used_at, + PaymentMethodUpdate::NetworkTokenDataUpdate { + network_token_requestor_reference_id, + network_token_locker_id, + network_token_payment_method_data, } => Self { - metadata, + metadata: None, payment_method_data: None, - last_used_at: Some(last_used_at), - network_transaction_id: None, + last_used_at: None, status: None, locker_id: None, payment_method: None, connector_mandate_details: None, updated_by: None, + payment_method_issuer: None, payment_method_type: None, last_modified: common_utils::date_time::now(), - network_token_locker_id: None, + network_transaction_id: None, + network_token_requestor_reference_id, + network_token_locker_id, + network_token_payment_method_data, + scheme: None, + }, + PaymentMethodUpdate::ConnectorNetworkTransactionIdAndMandateDetailsUpdate { + connector_mandate_details, + network_transaction_id, + } => Self { + connector_mandate_details: connector_mandate_details + .map(|mandate_details| mandate_details.expose()), + network_transaction_id: network_transaction_id.map(|txn_id| txn_id.expose()), + last_modified: common_utils::date_time::now(), + status: None, + metadata: None, + payment_method_data: None, + last_used_at: None, + locker_id: None, + payment_method: None, + updated_by: None, + payment_method_issuer: None, + payment_method_type: None, network_token_requestor_reference_id: None, + network_token_locker_id: None, network_token_payment_method_data: None, + scheme: None, }, + } + } +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl From for PaymentMethodUpdateInternal { + fn from(payment_method_update: PaymentMethodUpdate) -> Self { + match payment_method_update { PaymentMethodUpdate::PaymentMethodDataUpdate { payment_method_data, } => Self { - metadata: None, payment_method_data, last_used_at: None, network_transaction_id: None, status: None, locker_id: None, - payment_method: None, + payment_method_type_v2: None, connector_mandate_details: None, updated_by: None, - payment_method_type: None, + payment_method_subtype: None, last_modified: common_utils::date_time::now(), network_token_locker_id: None, network_token_requestor_reference_id: None, network_token_payment_method_data: None, + locker_fingerprint_id: None, }, PaymentMethodUpdate::LastUsedUpdate { last_used_at } => Self { - metadata: None, payment_method_data: None, last_used_at: Some(last_used_at), network_transaction_id: None, status: None, locker_id: None, - payment_method: None, + payment_method_type_v2: None, connector_mandate_details: None, updated_by: None, - payment_method_type: None, + payment_method_subtype: None, last_modified: common_utils::date_time::now(), network_token_locker_id: None, network_token_requestor_reference_id: None, network_token_payment_method_data: None, + locker_fingerprint_id: None, }, PaymentMethodUpdate::UpdatePaymentMethodDataAndLastUsed { payment_method_data, last_used_at, + .. } => Self { - metadata: None, payment_method_data, last_used_at: Some(last_used_at), network_transaction_id: None, status: None, locker_id: None, - payment_method: None, + payment_method_type_v2: None, connector_mandate_details: None, updated_by: None, - payment_method_type: None, + payment_method_subtype: None, last_modified: common_utils::date_time::now(), network_token_locker_id: None, network_token_requestor_reference_id: None, network_token_payment_method_data: None, + locker_fingerprint_id: None, }, PaymentMethodUpdate::NetworkTransactionIdAndStatusUpdate { network_transaction_id, status, } => Self { - metadata: None, payment_method_data: None, last_used_at: None, network_transaction_id, status, locker_id: None, - payment_method: None, + payment_method_type_v2: None, connector_mandate_details: None, updated_by: None, - payment_method_type: None, + payment_method_subtype: None, last_modified: common_utils::date_time::now(), network_token_locker_id: None, network_token_requestor_reference_id: None, network_token_payment_method_data: None, + locker_fingerprint_id: None, }, PaymentMethodUpdate::StatusUpdate { status } => Self { - metadata: None, payment_method_data: None, last_used_at: None, network_transaction_id: None, status, locker_id: None, - payment_method: None, + payment_method_type_v2: None, connector_mandate_details: None, updated_by: None, - payment_method_type: None, + payment_method_subtype: None, last_modified: common_utils::date_time::now(), network_token_locker_id: None, network_token_requestor_reference_id: None, network_token_payment_method_data: None, + locker_fingerprint_id: None, }, PaymentMethodUpdate::AdditionalDataUpdate { payment_method_data, status, locker_id, - payment_method, - payment_method_type, + payment_method_type_v2, + payment_method_subtype, network_token_requestor_reference_id, network_token_locker_id, network_token_payment_method_data, + locker_fingerprint_id, } => Self { - metadata: None, payment_method_data, last_used_at: None, network_transaction_id: None, status, locker_id, - payment_method, + payment_method_type_v2, connector_mandate_details: None, updated_by: None, - payment_method_type, + payment_method_subtype, last_modified: common_utils::date_time::now(), network_token_requestor_reference_id, network_token_locker_id, network_token_payment_method_data, + locker_fingerprint_id, }, PaymentMethodUpdate::ConnectorMandateDetailsUpdate { connector_mandate_details, } => Self { - metadata: None, payment_method_data: None, last_used_at: None, status: None, locker_id: None, - payment_method: None, + payment_method_type_v2: None, connector_mandate_details, network_transaction_id: None, updated_by: None, - payment_method_type: None, + payment_method_subtype: None, last_modified: common_utils::date_time::now(), network_token_locker_id: None, network_token_requestor_reference_id: None, network_token_payment_method_data: None, + locker_fingerprint_id: None, }, } } @@ -811,9 +894,6 @@ impl From<&PaymentMethodNew> for PaymentMethod { locker_id: payment_method_new.locker_id.clone(), created_at: payment_method_new.created_at, last_modified: payment_method_new.last_modified, - payment_method: payment_method_new.payment_method, - payment_method_type: payment_method_new.payment_method_type, - metadata: payment_method_new.metadata.clone(), payment_method_data: payment_method_new.payment_method_data.clone(), last_used_at: payment_method_new.last_used_at, connector_mandate_details: payment_method_new.connector_mandate_details.clone(), @@ -825,8 +905,10 @@ impl From<&PaymentMethodNew> for PaymentMethod { payment_method_billing_address: payment_method_new .payment_method_billing_address .clone(), - id: payment_method_new.id.clone(), locker_fingerprint_id: payment_method_new.locker_fingerprint_id.clone(), + payment_method_type_v2: payment_method_new.payment_method_type_v2, + payment_method_subtype: payment_method_new.payment_method_subtype, + id: payment_method_new.id.clone(), version: payment_method_new.version, network_token_requestor_reference_id: payment_method_new .network_token_requestor_reference_id @@ -838,3 +920,53 @@ impl From<&PaymentMethodNew> for PaymentMethod { } } } + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct PaymentsMandateReferenceRecord { + pub connector_mandate_id: String, + pub payment_method_type: Option, + pub original_payment_authorized_amount: Option, + pub original_payment_authorized_currency: Option, + pub mandate_metadata: Option, + pub connector_mandate_status: Option, + pub connector_mandate_request_reference_id: Option, +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct PaymentsMandateReferenceRecord { + pub connector_mandate_id: String, + pub payment_method_subtype: Option, + pub original_payment_authorized_amount: Option, + pub original_payment_authorized_currency: Option, + pub mandate_metadata: Option, + pub connector_mandate_status: Option, + pub connector_mandate_request_reference_id: Option, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, diesel::AsExpression)] +#[diesel(sql_type = diesel::sql_types::Jsonb)] +pub struct PaymentsMandateReference( + pub HashMap, +); + +impl std::ops::Deref for PaymentsMandateReference { + type Target = + HashMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for PaymentsMandateReference { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +common_utils::impl_to_sql_from_sql_json!(PaymentsMandateReference); diff --git a/crates/diesel_models/src/query.rs b/crates/diesel_models/src/query.rs index f966e90acba9..8eb0a44f5dd7 100644 --- a/crates/diesel_models/src/query.rs +++ b/crates/diesel_models/src/query.rs @@ -13,6 +13,7 @@ pub mod blocklist_fingerprint; pub mod customers; pub mod dashboard_metadata; pub mod dispute; +pub mod dynamic_routing_stats; pub mod events; pub mod file; pub mod fraud_check; @@ -33,6 +34,7 @@ pub mod payout_attempt; pub mod payouts; pub mod process_tracker; pub mod refund; +pub mod relay; pub mod reverse_lookup; pub mod role; pub mod routing_algorithm; diff --git a/crates/diesel_models/src/query/api_keys.rs b/crates/diesel_models/src/query/api_keys.rs index 5dd145010258..479e226c1d37 100644 --- a/crates/diesel_models/src/query/api_keys.rs +++ b/crates/diesel_models/src/query/api_keys.rs @@ -18,7 +18,7 @@ impl ApiKey { pub async fn update_by_merchant_id_key_id( conn: &PgPooledConn, merchant_id: common_utils::id_type::MerchantId, - key_id: String, + key_id: common_utils::id_type::ApiKeyId, api_key_update: ApiKeyUpdate, ) -> StorageResult { match generics::generic_update_with_unique_predicate_get_result::< @@ -57,7 +57,7 @@ impl ApiKey { pub async fn revoke_by_merchant_id_key_id( conn: &PgPooledConn, merchant_id: &common_utils::id_type::MerchantId, - key_id: &str, + key_id: &common_utils::id_type::ApiKeyId, ) -> StorageResult { generics::generic_delete::<::Table, _>( conn, @@ -71,7 +71,7 @@ impl ApiKey { pub async fn find_optional_by_merchant_id_key_id( conn: &PgPooledConn, merchant_id: &common_utils::id_type::MerchantId, - key_id: &str, + key_id: &common_utils::id_type::ApiKeyId, ) -> StorageResult> { generics::generic_find_one_optional::<::Table, _, _>( conn, diff --git a/crates/diesel_models/src/query/capture.rs b/crates/diesel_models/src/query/capture.rs index e194dc9f64c6..7a32e4109617 100644 --- a/crates/diesel_models/src/query/capture.rs +++ b/crates/diesel_models/src/query/capture.rs @@ -1,3 +1,4 @@ +use common_utils::types::ConnectorTransactionIdTrait; use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods}; use super::generics; @@ -67,3 +68,22 @@ impl Capture { .await } } + +impl ConnectorTransactionIdTrait for Capture { + fn get_optional_connector_transaction_id(&self) -> Option<&String> { + match self + .connector_capture_id + .as_ref() + .map(|capture_id| capture_id.get_txn_id(self.connector_capture_data.as_ref())) + .transpose() + { + Ok(capture_id) => capture_id, + + // In case hashed data is missing from DB, use the hashed ID as connector transaction ID + Err(_) => self + .connector_capture_id + .as_ref() + .map(|txn_id| txn_id.get_id()), + } + } +} diff --git a/crates/diesel_models/src/query/customers.rs b/crates/diesel_models/src/query/customers.rs index cf6e8fa645f4..5caad88c8d64 100644 --- a/crates/diesel_models/src/query/customers.rs +++ b/crates/diesel_models/src/query/customers.rs @@ -33,7 +33,7 @@ impl Customer { #[cfg(all(feature = "v2", feature = "customer_v2"))] pub async fn update_by_id( conn: &PgPooledConn, - id: String, + id: id_type::GlobalCustomerId, customer: CustomerUpdateInternal, ) -> StorageResult { match generics::generic_update_by_id::<::Table, _, _, _>( @@ -54,7 +54,10 @@ impl Customer { } #[cfg(all(feature = "v2", feature = "customer_v2"))] - pub async fn find_by_global_id(conn: &PgPooledConn, id: &str) -> StorageResult { + pub async fn find_by_global_id( + conn: &PgPooledConn, + id: &id_type::GlobalCustomerId, + ) -> StorageResult { generics::generic_find_by_id::<::Table, _, _>(conn, id.to_owned()).await } diff --git a/crates/diesel_models/src/query/dynamic_routing_stats.rs b/crates/diesel_models/src/query/dynamic_routing_stats.rs new file mode 100644 index 000000000000..f6771cd103d9 --- /dev/null +++ b/crates/diesel_models/src/query/dynamic_routing_stats.rs @@ -0,0 +1,11 @@ +use super::generics; +use crate::{ + dynamic_routing_stats::{DynamicRoutingStats, DynamicRoutingStatsNew}, + PgPooledConn, StorageResult, +}; + +impl DynamicRoutingStatsNew { + pub async fn insert(self, conn: &PgPooledConn) -> StorageResult { + generics::generic_insert(conn, self).await + } +} diff --git a/crates/diesel_models/src/query/generics.rs b/crates/diesel_models/src/query/generics.rs index 682766679fd7..bf3238ab4fea 100644 --- a/crates/diesel_models/src/query/generics.rs +++ b/crates/diesel_models/src/query/generics.rs @@ -25,8 +25,6 @@ use router_env::logger; use crate::{errors, PgPooledConn, StorageResult}; pub mod db_metrics { - use router_env::opentelemetry::KeyValue; - #[derive(Debug)] pub enum DatabaseOperation { FindOne, @@ -51,18 +49,14 @@ pub mod db_metrics { let table_name = std::any::type_name::().rsplit("::").nth(1); - let attributes = [ - KeyValue::new("table", table_name.unwrap_or("undefined")), - KeyValue::new("operation", format!("{:?}", operation)), - ]; - - crate::metrics::DATABASE_CALLS_COUNT.add(&crate::metrics::CONTEXT, 1, &attributes); - crate::metrics::DATABASE_CALL_TIME.record( - &crate::metrics::CONTEXT, - time_elapsed.as_secs_f64(), - &attributes, + let attributes = router_env::metric_attributes!( + ("table", table_name.unwrap_or("undefined")), + ("operation", format!("{:?}", operation)) ); + crate::metrics::DATABASE_CALLS_COUNT.add(1, attributes); + crate::metrics::DATABASE_CALL_TIME.record(time_elapsed.as_secs_f64(), attributes); + output } } diff --git a/crates/diesel_models/src/query/mandate.rs b/crates/diesel_models/src/query/mandate.rs index 9f5a402c1772..96e6850f0fa3 100644 --- a/crates/diesel_models/src/query/mandate.rs +++ b/crates/diesel_models/src/query/mandate.rs @@ -63,8 +63,23 @@ impl Mandate { //Fix this function once V2 mandate is schema is being built #[cfg(all(feature = "v2", feature = "customer_v2"))] - pub async fn find_by_global_id(_conn: &PgPooledConn, _id: &str) -> StorageResult> { - todo!() + pub async fn find_by_global_customer_id( + conn: &PgPooledConn, + customer_id: &common_utils::id_type::GlobalCustomerId, + ) -> StorageResult> { + generics::generic_filter::< + ::Table, + _, + <::Table as Table>::PrimaryKey, + _, + >( + conn, + dsl::customer_id.eq(customer_id.to_owned()), + None, + None, + None, + ) + .await } pub async fn update_by_merchant_id_mandate_id( diff --git a/crates/diesel_models/src/query/merchant_account.rs b/crates/diesel_models/src/query/merchant_account.rs index fd5a171b7eb8..03945646014b 100644 --- a/crates/diesel_models/src/query/merchant_account.rs +++ b/crates/diesel_models/src/query/merchant_account.rs @@ -121,6 +121,26 @@ impl MerchantAccount { .await } + pub async fn list_all_merchant_accounts( + conn: &PgPooledConn, + limit: u32, + offset: Option, + ) -> StorageResult> { + generics::generic_filter::< + ::Table, + _, + <::Table as Table>::PrimaryKey, + _, + >( + conn, + dsl_identifier.ne_all(vec![""]), + Some(i64::from(limit)), + offset.map(i64::from), + None, + ) + .await + } + pub async fn update_all_merchant_accounts( conn: &PgPooledConn, merchant_account: MerchantAccountUpdateInternal, diff --git a/crates/diesel_models/src/query/merchant_connector_account.rs b/crates/diesel_models/src/query/merchant_connector_account.rs index 0f0954420047..7abc0d6efde7 100644 --- a/crates/diesel_models/src/query/merchant_connector_account.rs +++ b/crates/diesel_models/src/query/merchant_connector_account.rs @@ -241,4 +241,22 @@ impl MerchantConnectorAccount { ) .await } + + pub async fn list_enabled_by_profile_id( + conn: &PgPooledConn, + profile_id: &common_utils::id_type::ProfileId, + connector_type: common_enums::ConnectorType, + ) -> StorageResult> { + generics::generic_filter::<::Table, _, _, _>( + conn, + dsl::profile_id + .eq(profile_id.to_owned()) + .and(dsl::disabled.eq(false)) + .and(dsl::connector_type.eq(connector_type)), + None, + None, + Some(dsl::created_at.asc()), + ) + .await + } } diff --git a/crates/diesel_models/src/query/payment_attempt.rs b/crates/diesel_models/src/query/payment_attempt.rs index ad056aef6d07..9dd13e5b0300 100644 --- a/crates/diesel_models/src/query/payment_attempt.rs +++ b/crates/diesel_models/src/query/payment_attempt.rs @@ -24,7 +24,7 @@ use crate::{ impl PaymentAttemptNew { pub async fn insert(self, conn: &PgPooledConn) -> StorageResult { - generics::generic_insert(conn, self.populate_derived_fields()).await + generics::generic_insert(conn, self).await } } @@ -68,11 +68,7 @@ impl PaymentAttempt { _, _, _, - >( - conn, - dsl::id.eq(self.id.to_owned()), - payment_attempt.populate_derived_fields(&self), - ) + >(conn, dsl::id.eq(self.id.to_owned()), payment_attempt) .await { Err(error) => match error.current_context() { @@ -101,14 +97,14 @@ impl PaymentAttempt { #[cfg(feature = "v1")] pub async fn find_by_connector_transaction_id_payment_id_merchant_id( conn: &PgPooledConn, - connector_transaction_id: &str, + connector_transaction_id: &common_utils::types::ConnectorTransactionId, payment_id: &common_utils::id_type::PaymentId, merchant_id: &common_utils::id_type::MerchantId, ) -> StorageResult { generics::generic_find_one::<::Table, _, _>( conn, dsl::connector_transaction_id - .eq(connector_transaction_id.to_owned()) + .eq(connector_transaction_id.get_id().to_owned()) .and(dsl::payment_id.eq(payment_id.to_owned())) .and(dsl::merchant_id.eq(merchant_id.to_owned())), ) @@ -171,11 +167,50 @@ impl PaymentAttempt { merchant_id: &common_utils::id_type::MerchantId, connector_txn_id: &str, ) -> StorageResult { + let (txn_id, txn_data) = common_utils::types::ConnectorTransactionId::form_id_and_data( + connector_txn_id.to_string(), + ); + let connector_transaction_id = txn_id + .get_txn_id(txn_data.as_ref()) + .change_context(DatabaseError::Others) + .attach_printable_lazy(|| { + format!( + "Failed to retrieve txn_id for ({:?}, {:?})", + txn_id, txn_data + ) + })?; generics::generic_find_one::<::Table, _, _>( conn, dsl::merchant_id .eq(merchant_id.to_owned()) - .and(dsl::connector_transaction_id.eq(connector_txn_id.to_owned())), + .and(dsl::connector_transaction_id.eq(connector_transaction_id.to_owned())), + ) + .await + } + + #[cfg(feature = "v2")] + pub async fn find_by_profile_id_connector_transaction_id( + conn: &PgPooledConn, + profile_id: &common_utils::id_type::ProfileId, + connector_txn_id: &str, + ) -> StorageResult { + let (txn_id, txn_data) = common_utils::types::ConnectorTransactionId::form_id_and_data( + connector_txn_id.to_string(), + ); + let connector_transaction_id = txn_id + .get_txn_id(txn_data.as_ref()) + .change_context(DatabaseError::Others) + .attach_printable_lazy(|| { + format!( + "Failed to retrieve txn_id for ({:?}, {:?})", + txn_id, txn_data + ) + })?; + generics::generic_find_one::<::Table, _, _>( + conn, + dsl::profile_id + .eq(profile_id.to_owned()) + .and(dsl::connector_payment_id.eq(connector_transaction_id.to_owned())), ) .await } @@ -196,7 +231,10 @@ impl PaymentAttempt { } #[cfg(feature = "v2")] - pub async fn find_by_id(conn: &PgPooledConn, id: &str) -> StorageResult { + pub async fn find_by_id( + conn: &PgPooledConn, + id: &common_utils::id_type::GlobalAttemptId, + ) -> StorageResult { generics::generic_find_one::<::Table, _, _>( conn, dsl::id.eq(id.to_owned()), @@ -369,8 +407,8 @@ impl PaymentAttempt { payment_method: Option>, payment_method_type: Option>, authentication_type: Option>, - profile_id_list: Option>, merchant_connector_id: Option>, + card_network: Option>, ) -> StorageResult { let mut filter = ::table() .count() @@ -394,17 +432,26 @@ impl PaymentAttempt { if let Some(merchant_connector_id) = merchant_connector_id { filter = filter.filter(dsl::merchant_connector_id.eq_any(merchant_connector_id)) } - if let Some(profile_id_list) = profile_id_list { - filter = filter.filter(dsl::profile_id.eq_any(profile_id_list)) + if let Some(card_network) = card_network { + filter = filter.filter(dsl::card_network.eq_any(card_network)) } + router_env::logger::debug!(query = %debug_query::(&filter).to_string()); - db_metrics::track_database_call::<::Table, _, _>( + // TODO: Remove these logs after debugging the issue for delay in count query + let start_time = std::time::Instant::now(); + router_env::logger::debug!("Executing count query start_time: {:?}", start_time); + let result = db_metrics::track_database_call::<::Table, _, _>( filter.get_result_async::(conn), db_metrics::DatabaseOperation::Filter, ) .await .change_context(DatabaseError::Others) - .attach_printable("Error filtering count of payments") + .attach_printable("Error filtering count of payments"); + + let duration = start_time.elapsed(); + router_env::logger::debug!("Completed count query in {:?}", duration); + + result } } diff --git a/crates/diesel_models/src/query/payment_intent.rs b/crates/diesel_models/src/query/payment_intent.rs index fb23fc60107d..4f4099eca015 100644 --- a/crates/diesel_models/src/query/payment_intent.rs +++ b/crates/diesel_models/src/query/payment_intent.rs @@ -7,9 +7,7 @@ use crate::schema::payment_intent::dsl; use crate::schema_v2::payment_intent::dsl; use crate::{ errors, - payment_intent::{ - PaymentIntent, PaymentIntentNew, PaymentIntentUpdate, PaymentIntentUpdateInternal, - }, + payment_intent::{self, PaymentIntent, PaymentIntentNew}, PgPooledConn, StorageResult, }; @@ -24,12 +22,12 @@ impl PaymentIntent { pub async fn update( self, conn: &PgPooledConn, - payment_intent: PaymentIntentUpdate, + payment_intent_update: payment_intent::PaymentIntentUpdateInternal, ) -> StorageResult { match generics::generic_update_by_id::<::Table, _, _, _>( conn, self.id.to_owned(), - PaymentIntentUpdateInternal::from(payment_intent), + payment_intent_update, ) .await { @@ -53,14 +51,14 @@ impl PaymentIntent { pub async fn update( self, conn: &PgPooledConn, - payment_intent: PaymentIntentUpdate, + payment_intent: payment_intent::PaymentIntentUpdate, ) -> StorageResult { match generics::generic_update_with_results::<::Table, _, _, _>( conn, dsl::payment_id .eq(self.payment_id.to_owned()) .and(dsl::merchant_id.eq(self.merchant_id.to_owned())), - PaymentIntentUpdateInternal::from(payment_intent), + payment_intent::PaymentIntentUpdateInternal::from(payment_intent), ) .await { diff --git a/crates/diesel_models/src/query/payment_method.rs b/crates/diesel_models/src/query/payment_method.rs index 321c7a6829c5..d4ad52ae146d 100644 --- a/crates/diesel_models/src/query/payment_method.rs +++ b/crates/diesel_models/src/query/payment_method.rs @@ -126,16 +126,6 @@ impl PaymentMethod { .await } - // Need to fix this function once we start moving to v2 for payment method - #[cfg(all(feature = "v2", feature = "customer_v2"))] - pub async fn find_by_global_id( - _conn: &PgPooledConn, - _id: &str, - _limit: Option, - ) -> StorageResult> { - todo!() - } - pub async fn get_count_by_customer_id_merchant_id_status( conn: &PgPooledConn, customer_id: &common_utils::id_type::CustomerId, @@ -219,9 +209,9 @@ impl PaymentMethod { .await } - pub async fn find_by_customer_id_merchant_id_status( + pub async fn find_by_global_customer_id_merchant_id_status( conn: &PgPooledConn, - customer_id: &common_utils::id_type::CustomerId, + customer_id: &common_utils::id_type::GlobalCustomerId, merchant_id: &common_utils::id_type::MerchantId, status: storage_enums::PaymentMethodStatus, limit: Option, @@ -239,6 +229,21 @@ impl PaymentMethod { .await } + pub async fn find_by_global_customer_id( + conn: &PgPooledConn, + customer_id: &common_utils::id_type::GlobalCustomerId, + limit: Option, + ) -> StorageResult> { + generics::generic_filter::<::Table, _, _, _>( + conn, + dsl::customer_id.eq(customer_id.to_owned()), + limit, + None, + Some(dsl::last_used_at.desc()), + ) + .await + } + pub async fn update_with_id( self, conn: &PgPooledConn, diff --git a/crates/diesel_models/src/query/relay.rs b/crates/diesel_models/src/query/relay.rs new file mode 100644 index 000000000000..034446fe6b51 --- /dev/null +++ b/crates/diesel_models/src/query/relay.rs @@ -0,0 +1,49 @@ +use diesel::{associations::HasTable, ExpressionMethods}; + +use super::generics; +use crate::{ + errors, + relay::{Relay, RelayNew, RelayUpdateInternal}, + schema::relay::dsl, + PgPooledConn, StorageResult, +}; + +impl RelayNew { + pub async fn insert(self, conn: &PgPooledConn) -> StorageResult { + generics::generic_insert(conn, self).await + } +} + +impl Relay { + pub async fn update( + self, + conn: &PgPooledConn, + relay: RelayUpdateInternal, + ) -> StorageResult { + match generics::generic_update_with_unique_predicate_get_result::< + ::Table, + _, + _, + _, + >(conn, dsl::id.eq(self.id.to_owned()), relay) + .await + { + Err(error) => match error.current_context() { + errors::DatabaseError::NoFieldsToUpdate => Ok(self), + _ => Err(error), + }, + result => result, + } + } + + pub async fn find_by_id( + conn: &PgPooledConn, + id: &common_utils::id_type::RelayId, + ) -> StorageResult { + generics::generic_find_one::<::Table, _, _>( + conn, + dsl::id.eq(id.to_owned()), + ) + .await + } +} diff --git a/crates/diesel_models/src/query/role.rs b/crates/diesel_models/src/query/role.rs index 065a5b6e114e..6f6a1404ee2c 100644 --- a/crates/diesel_models/src/query/role.rs +++ b/crates/diesel_models/src/query/role.rs @@ -26,6 +26,7 @@ impl Role { .await } + // TODO: Remove once find_by_role_id_in_lineage is stable pub async fn find_by_role_id_in_merchant_scope( conn: &PgPooledConn, role_id: &str, @@ -43,7 +44,27 @@ impl Role { .await } - pub async fn find_by_role_id_in_org_scope( + pub async fn find_by_role_id_in_lineage( + conn: &PgPooledConn, + role_id: &str, + merchant_id: &id_type::MerchantId, + org_id: &id_type::OrganizationId, + ) -> StorageResult { + generics::generic_find_one::<::Table, _, _>( + conn, + dsl::role_id + .eq(role_id.to_owned()) + .and(dsl::org_id.eq(org_id.to_owned())) + .and( + dsl::scope.eq(RoleScope::Organization).or(dsl::merchant_id + .eq(merchant_id.to_owned()) + .and(dsl::scope.eq(RoleScope::Merchant))), + ), + ) + .await + } + + pub async fn find_by_role_id_and_org_id( conn: &PgPooledConn, role_id: &str, org_id: &id_type::OrganizationId, @@ -88,9 +109,11 @@ impl Role { merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, ) -> StorageResult> { - let predicate = dsl::merchant_id.eq(merchant_id.to_owned()).or(dsl::org_id - .eq(org_id.to_owned()) - .and(dsl::scope.eq(RoleScope::Organization))); + let predicate = dsl::org_id.eq(org_id.to_owned()).and( + dsl::scope.eq(RoleScope::Organization).or(dsl::merchant_id + .eq(merchant_id.to_owned()) + .and(dsl::scope.eq(RoleScope::Merchant))), + ); generics::generic_filter::<::Table, _, _, _>( conn, @@ -115,9 +138,10 @@ impl Role { if let Some(merchant_id) = merchant_id { query = query.filter( - dsl::merchant_id + (dsl::merchant_id .eq(merchant_id) - .or(dsl::scope.eq(RoleScope::Organization)), + .and(dsl::scope.eq(RoleScope::Merchant))) + .or(dsl::scope.eq(RoleScope::Organization)), ); } diff --git a/crates/diesel_models/src/query/user.rs b/crates/diesel_models/src/query/user.rs index 2bd403a847b3..1f6e16702fb7 100644 --- a/crates/diesel_models/src/query/user.rs +++ b/crates/diesel_models/src/query/user.rs @@ -1,6 +1,8 @@ use common_utils::pii; use diesel::{associations::HasTable, ExpressionMethods}; + pub mod sample_data; +pub mod theme; use crate::{ query::generics, schema::users::dsl as users_dsl, user::*, PgPooledConn, StorageResult, diff --git a/crates/diesel_models/src/query/user/sample_data.rs b/crates/diesel_models/src/query/user/sample_data.rs index b5400f570a33..b97ea39b82d9 100644 --- a/crates/diesel_models/src/query/user/sample_data.rs +++ b/crates/diesel_models/src/query/user/sample_data.rs @@ -12,11 +12,13 @@ use crate::schema_v2::{ payment_attempt::dsl as payment_attempt_dsl, payment_intent::dsl as payment_intent_dsl, }; use crate::{ - errors, schema::refund::dsl as refund_dsl, user::sample_data::PaymentAttemptBatchNew, - PaymentAttempt, PaymentIntent, PaymentIntentNew, PgPooledConn, Refund, RefundNew, - StorageResult, + errors, + schema::{dispute::dsl as dispute_dsl, refund::dsl as refund_dsl}, + user, Dispute, DisputeNew, PaymentAttempt, PaymentIntent, PaymentIntentNew, PgPooledConn, + Refund, RefundNew, StorageResult, }; +#[cfg(feature = "v1")] pub async fn insert_payment_intents( conn: &PgPooledConn, batch: Vec, @@ -31,9 +33,11 @@ pub async fn insert_payment_intents( .change_context(errors::DatabaseError::Others) .attach_printable("Error while inserting payment intents") } + +#[cfg(feature = "v1")] pub async fn insert_payment_attempts( conn: &PgPooledConn, - batch: Vec, + batch: Vec, ) -> StorageResult> { let query = diesel::insert_into(::table()).values(batch); @@ -61,6 +65,21 @@ pub async fn insert_refunds( .attach_printable("Error while inserting refunds") } +pub async fn insert_disputes( + conn: &PgPooledConn, + batch: Vec, +) -> StorageResult> { + let query = diesel::insert_into(::table()).values(batch); + + logger::debug!(query = %debug_query::(&query).to_string()); + + query + .get_results_async(conn) + .await + .change_context(errors::DatabaseError::Others) + .attach_printable("Error while inserting disputes") +} + #[cfg(feature = "v1")] pub async fn delete_payment_intents( conn: &PgPooledConn, @@ -165,3 +184,29 @@ pub async fn delete_refunds( _ => Ok(result), }) } + +pub async fn delete_disputes( + conn: &PgPooledConn, + merchant_id: &common_utils::id_type::MerchantId, +) -> StorageResult> { + let query = diesel::delete(::table()) + .filter(dispute_dsl::merchant_id.eq(merchant_id.to_owned())) + .filter(dispute_dsl::dispute_id.like("test_%")); + + logger::debug!(query = %debug_query::(&query).to_string()); + + query + .get_results_async(conn) + .await + .change_context(errors::DatabaseError::Others) + .attach_printable("Error while deleting disputes") + .and_then(|result| match result.len() { + n if n > 0 => { + logger::debug!("{n} records deleted"); + Ok(result) + } + 0 => Err(error_stack::report!(errors::DatabaseError::NotFound) + .attach_printable("No records deleted")), + _ => Ok(result), + }) +} diff --git a/crates/diesel_models/src/query/user/theme.rs b/crates/diesel_models/src/query/user/theme.rs new file mode 100644 index 000000000000..945ce1a9bb22 --- /dev/null +++ b/crates/diesel_models/src/query/user/theme.rs @@ -0,0 +1,147 @@ +use async_bb8_diesel::AsyncRunQueryDsl; +use common_utils::types::theme::ThemeLineage; +use diesel::{ + associations::HasTable, + debug_query, + pg::Pg, + result::Error as DieselError, + sql_types::{Bool, Nullable}, + BoolExpressionMethods, ExpressionMethods, NullableExpressionMethods, QueryDsl, +}; +use error_stack::{report, ResultExt}; +use router_env::logger; + +use crate::{ + errors::DatabaseError, + query::generics::{ + self, + db_metrics::{track_database_call, DatabaseOperation}, + }, + schema::themes::dsl, + user::theme::{Theme, ThemeNew}, + PgPooledConn, StorageResult, +}; + +impl ThemeNew { + pub async fn insert(self, conn: &PgPooledConn) -> StorageResult { + generics::generic_insert(conn, self).await + } +} + +impl Theme { + fn lineage_filter( + lineage: ThemeLineage, + ) -> Box< + dyn diesel::BoxableExpression<::Table, Pg, SqlType = Nullable> + + 'static, + > { + match lineage { + ThemeLineage::Tenant { tenant_id } => Box::new( + dsl::tenant_id + .eq(tenant_id) + .and(dsl::org_id.is_null()) + .and(dsl::merchant_id.is_null()) + .and(dsl::profile_id.is_null()) + .nullable(), + ), + ThemeLineage::Organization { tenant_id, org_id } => Box::new( + dsl::tenant_id + .eq(tenant_id) + .and(dsl::org_id.eq(org_id)) + .and(dsl::merchant_id.is_null()) + .and(dsl::profile_id.is_null()), + ), + ThemeLineage::Merchant { + tenant_id, + org_id, + merchant_id, + } => Box::new( + dsl::tenant_id + .eq(tenant_id) + .and(dsl::org_id.eq(org_id)) + .and(dsl::merchant_id.eq(merchant_id)) + .and(dsl::profile_id.is_null()), + ), + ThemeLineage::Profile { + tenant_id, + org_id, + merchant_id, + profile_id, + } => Box::new( + dsl::tenant_id + .eq(tenant_id) + .and(dsl::org_id.eq(org_id)) + .and(dsl::merchant_id.eq(merchant_id)) + .and(dsl::profile_id.eq(profile_id)), + ), + } + } + + pub async fn find_by_theme_id(conn: &PgPooledConn, theme_id: String) -> StorageResult { + generics::generic_find_one::<::Table, _, _>( + conn, + dsl::theme_id.eq(theme_id), + ) + .await + } + + pub async fn find_most_specific_theme_in_lineage( + conn: &PgPooledConn, + lineage: ThemeLineage, + ) -> StorageResult { + let query = ::table().into_boxed(); + + let query = + lineage + .get_same_and_higher_lineages() + .into_iter() + .fold(query, |mut query, lineage| { + query = query.or_filter(Self::lineage_filter(lineage)); + query + }); + + logger::debug!(query = %debug_query::(&query).to_string()); + + let data: Vec = match track_database_call::( + query.get_results_async(conn), + DatabaseOperation::Filter, + ) + .await + { + Ok(value) => Ok(value), + Err(err) => match err { + DieselError::NotFound => Err(report!(err)).change_context(DatabaseError::NotFound), + _ => Err(report!(err)).change_context(DatabaseError::Others), + }, + }?; + + data.into_iter() + .min_by_key(|theme| theme.entity_type) + .ok_or(report!(DatabaseError::NotFound)) + } + + pub async fn find_by_lineage( + conn: &PgPooledConn, + lineage: ThemeLineage, + ) -> StorageResult { + generics::generic_find_one::<::Table, _, _>( + conn, + Self::lineage_filter(lineage), + ) + .await + } + + pub async fn delete_by_theme_id_and_lineage( + conn: &PgPooledConn, + theme_id: String, + lineage: ThemeLineage, + ) -> StorageResult { + generics::generic_delete_one_with_result::<::Table, _, _>( + conn, + dsl::theme_id + .eq(theme_id) + .and(Self::lineage_filter(lineage)), + ) + .await + } +} diff --git a/crates/diesel_models/src/query/user_role.rs b/crates/diesel_models/src/query/user_role.rs index 1cd25e54cb3e..bb07f6718245 100644 --- a/crates/diesel_models/src/query/user_role.rs +++ b/crates/diesel_models/src/query/user_role.rs @@ -1,11 +1,14 @@ use async_bb8_diesel::AsyncRunQueryDsl; use common_utils::id_type; use diesel::{ - associations::HasTable, debug_query, pg::Pg, result::Error as DieselError, + associations::HasTable, + debug_query, + pg::Pg, + result::Error as DieselError, + sql_types::{Bool, Nullable}, BoolExpressionMethods, ExpressionMethods, QueryDsl, }; use error_stack::{report, ResultExt}; -use router_env::logger; use crate::{ enums::{UserRoleVersion, UserStatus}, @@ -23,105 +26,70 @@ impl UserRoleNew { } impl UserRole { - pub async fn insert_multiple_user_roles( - conn: &PgPooledConn, - user_roles: Vec, - ) -> StorageResult> { - let query = diesel::insert_into(::table()).values(user_roles); - - logger::debug!(query = %debug_query::(&query).to_string()); - - query - .get_results_async(conn) - .await - .change_context(errors::DatabaseError::Others) - .attach_printable("Error while inserting user_roles") - } - - pub async fn find_by_user_id( - conn: &PgPooledConn, - user_id: String, - version: UserRoleVersion, - ) -> StorageResult { - generics::generic_find_one::<::Table, _, _>( - conn, - dsl::user_id.eq(user_id).and(dsl::version.eq(version)), - ) - .await - } - - pub async fn find_by_user_id_merchant_id( - conn: &PgPooledConn, - user_id: String, - merchant_id: id_type::MerchantId, - version: UserRoleVersion, - ) -> StorageResult { - generics::generic_find_one::<::Table, _, _>( - conn, - dsl::user_id - .eq(user_id) - .and(dsl::merchant_id.eq(merchant_id)) - .and(dsl::version.eq(version)), - ) - .await - } - - pub async fn list_by_user_id( - conn: &PgPooledConn, - user_id: String, - version: UserRoleVersion, - ) -> StorageResult> { - generics::generic_filter::<::Table, _, _, _>( - conn, - dsl::user_id.eq(user_id).and(dsl::version.eq(version)), - None, - None, - Some(dsl::created_at.asc()), - ) - .await - } - - pub async fn list_by_merchant_id( - conn: &PgPooledConn, - merchant_id: id_type::MerchantId, - version: UserRoleVersion, - ) -> StorageResult> { - generics::generic_filter::<::Table, _, _, _>( - conn, - dsl::merchant_id - .eq(merchant_id) - .and(dsl::version.eq(version)), - None, - None, - Some(dsl::created_at.asc()), + fn check_user_in_lineage( + tenant_id: id_type::TenantId, + org_id: Option, + merchant_id: Option, + profile_id: Option, + ) -> Box< + dyn diesel::BoxableExpression<::Table, Pg, SqlType = Nullable> + + 'static, + > { + // Checking in user roles, for a user in token hierarchy, only one of the relations will be true: + // either tenant level, org level, merchant level, or profile level + // Tenant-level: (tenant_id = ? && org_id = null && merchant_id = null && profile_id = null) + // Org-level: (org_id = ? && merchant_id = null && profile_id = null) + // Merchant-level: (org_id = ? && merchant_id = ? && profile_id = null) + // Profile-level: (org_id = ? && merchant_id = ? && profile_id = ?) + Box::new( + // Tenant-level condition + dsl::tenant_id + .eq(tenant_id.clone()) + .and(dsl::org_id.is_null()) + .and(dsl::merchant_id.is_null()) + .and(dsl::profile_id.is_null()) + .or( + // Org-level condition + dsl::tenant_id + .eq(tenant_id.clone()) + .and(dsl::org_id.eq(org_id.clone())) + .and(dsl::merchant_id.is_null()) + .and(dsl::profile_id.is_null()), + ) + .or( + // Merchant-level condition + dsl::tenant_id + .eq(tenant_id.clone()) + .and(dsl::org_id.eq(org_id.clone())) + .and(dsl::merchant_id.eq(merchant_id.clone())) + .and(dsl::profile_id.is_null()), + ) + .or( + // Profile-level condition + dsl::tenant_id + .eq(tenant_id) + .and(dsl::org_id.eq(org_id)) + .and(dsl::merchant_id.eq(merchant_id)) + .and(dsl::profile_id.eq(profile_id)), + ), ) - .await } - pub async fn find_by_user_id_org_id_merchant_id_profile_id( + pub async fn find_by_user_id_tenant_id_org_id_merchant_id_profile_id( conn: &PgPooledConn, user_id: String, + tenant_id: id_type::TenantId, org_id: id_type::OrganizationId, merchant_id: id_type::MerchantId, - profile_id: Option, + profile_id: id_type::ProfileId, version: UserRoleVersion, ) -> StorageResult { - // Checking in user roles, for a user in token hierarchy, only one of the relation will be true, either org level, merchant level or profile level - // (org_id = ? && merchant_id = null && profile_id = null) || (org_id = ? && merchant_id = ? && profile_id = null) || (org_id = ? && merchant_id = ? && profile_id = ?) - let check_lineage = dsl::org_id - .eq(org_id.clone()) - .and(dsl::merchant_id.is_null().and(dsl::profile_id.is_null())) - .or(dsl::org_id.eq(org_id.clone()).and( - dsl::merchant_id - .eq(merchant_id.clone()) - .and(dsl::profile_id.is_null()), - )) - .or(dsl::org_id.eq(org_id).and( - dsl::merchant_id - .eq(merchant_id) - //TODO: In case of None, profile_id = NULL its unexpected behaviour, after V1 profile id will not be option - .and(dsl::profile_id.eq(profile_id)), - )); + let check_lineage = Self::check_user_in_lineage( + tenant_id, + Some(org_id), + Some(merchant_id), + Some(profile_id), + ); let predicate = dsl::user_id .eq(user_id) @@ -131,31 +99,46 @@ impl UserRole { generics::generic_find_one::<::Table, _, _>(conn, predicate).await } - pub async fn update_by_user_id_org_id_merchant_id_profile_id( + #[allow(clippy::too_many_arguments)] + pub async fn update_by_user_id_tenant_id_org_id_merchant_id_profile_id( conn: &PgPooledConn, user_id: String, + tenant_id: id_type::TenantId, org_id: id_type::OrganizationId, - merchant_id: id_type::MerchantId, + merchant_id: Option, profile_id: Option, update: UserRoleUpdate, version: UserRoleVersion, ) -> StorageResult { - // Checking in user roles, for a user in token hierarchy, only one of the relation will be true, either org level, merchant level or profile level - // (org_id = ? && merchant_id = null && profile_id = null) || (org_id = ? && merchant_id = ? && profile_id = null) || (org_id = ? && merchant_id = ? && profile_id = ?) - let check_lineage = dsl::org_id - .eq(org_id.clone()) - .and(dsl::merchant_id.is_null().and(dsl::profile_id.is_null())) - .or(dsl::org_id.eq(org_id.clone()).and( - dsl::merchant_id - .eq(merchant_id.clone()) + let check_lineage = dsl::tenant_id + .eq(tenant_id.clone()) + .and(dsl::org_id.is_null()) + .and(dsl::merchant_id.is_null()) + .and(dsl::profile_id.is_null()) + .or( + // Org-level condition + dsl::tenant_id + .eq(tenant_id.clone()) + .and(dsl::org_id.eq(org_id.clone())) + .and(dsl::merchant_id.is_null()) .and(dsl::profile_id.is_null()), - )) - .or(dsl::org_id.eq(org_id).and( - dsl::merchant_id - .eq(merchant_id) - //TODO: In case of None, profile_id = NULL its unexpected behaviour, after V1 profile id will not be option + ) + .or( + // Merchant-level condition + dsl::tenant_id + .eq(tenant_id.clone()) + .and(dsl::org_id.eq(org_id.clone())) + .and(dsl::merchant_id.eq(merchant_id.clone())) + .and(dsl::profile_id.is_null()), + ) + .or( + // Profile-level condition + dsl::tenant_id + .eq(tenant_id) + .and(dsl::org_id.eq(org_id)) + .and(dsl::merchant_id.eq(merchant_id)) .and(dsl::profile_id.eq(profile_id)), - )); + ); let predicate = dsl::user_id .eq(user_id) @@ -171,30 +154,21 @@ impl UserRole { .await } - pub async fn delete_by_user_id_org_id_merchant_id_profile_id( + pub async fn delete_by_user_id_tenant_id_org_id_merchant_id_profile_id( conn: &PgPooledConn, user_id: String, + tenant_id: id_type::TenantId, org_id: id_type::OrganizationId, merchant_id: id_type::MerchantId, - profile_id: Option, + profile_id: id_type::ProfileId, version: UserRoleVersion, ) -> StorageResult { - // Checking in user roles, for a user in token hierarchy, only one of the relation will be true, either org level, merchant level or profile level - // (org_id = ? && merchant_id = null && profile_id = null) || (org_id = ? && merchant_id = ? && profile_id = null) || (org_id = ? && merchant_id = ? && profile_id = ?) - let check_lineage = dsl::org_id - .eq(org_id.clone()) - .and(dsl::merchant_id.is_null().and(dsl::profile_id.is_null())) - .or(dsl::org_id.eq(org_id.clone()).and( - dsl::merchant_id - .eq(merchant_id.clone()) - .and(dsl::profile_id.is_null()), - )) - .or(dsl::org_id.eq(org_id).and( - dsl::merchant_id - .eq(merchant_id) - //TODO: In case of None, profile_id = NULL its unexpected behaviour, after V1 profile id will not be option - .and(dsl::profile_id.eq(profile_id)), - )); + let check_lineage = Self::check_user_in_lineage( + tenant_id, + Some(org_id), + Some(merchant_id), + Some(profile_id), + ); let predicate = dsl::user_id .eq(user_id) @@ -209,6 +183,7 @@ impl UserRole { pub async fn generic_user_roles_list_for_user( conn: &PgPooledConn, user_id: String, + tenant_id: id_type::TenantId, org_id: Option, merchant_id: Option, profile_id: Option, @@ -218,7 +193,7 @@ impl UserRole { limit: Option, ) -> StorageResult> { let mut query = ::table() - .filter(dsl::user_id.eq(user_id)) + .filter(dsl::user_id.eq(user_id).and(dsl::tenant_id.eq(tenant_id))) .into_boxed(); if let Some(org_id) = org_id { @@ -267,16 +242,19 @@ impl UserRole { } } + #[allow(clippy::too_many_arguments)] pub async fn generic_user_roles_list_for_org_and_extra( conn: &PgPooledConn, user_id: Option, + tenant_id: id_type::TenantId, org_id: id_type::OrganizationId, merchant_id: Option, profile_id: Option, version: Option, + limit: Option, ) -> StorageResult> { let mut query = ::table() - .filter(dsl::org_id.eq(org_id)) + .filter(dsl::org_id.eq(org_id).and(dsl::tenant_id.eq(tenant_id))) .into_boxed(); if let Some(user_id) = user_id { @@ -295,6 +273,10 @@ impl UserRole { query = query.filter(dsl::version.eq(version)); } + if let Some(limit) = limit { + query = query.limit(limit.into()); + } + router_env::logger::debug!(query = %debug_query::(&query).to_string()); match generics::db_metrics::track_database_call::( diff --git a/crates/diesel_models/src/refund.rs b/crates/diesel_models/src/refund.rs index 42b0ffa620c6..758eba7255ae 100644 --- a/crates/diesel_models/src/refund.rs +++ b/crates/diesel_models/src/refund.rs @@ -1,6 +1,6 @@ use common_utils::{ pii, - types::{ChargeRefunds, MinorUnit}, + types::{ChargeRefunds, ConnectorTransactionId, ConnectorTransactionIdTrait, MinorUnit}, }; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; use serde::{Deserialize, Serialize}; @@ -25,9 +25,9 @@ pub struct Refund { pub refund_id: String, //merchant_reference id pub payment_id: common_utils::id_type::PaymentId, pub merchant_id: common_utils::id_type::MerchantId, - pub connector_transaction_id: String, + pub connector_transaction_id: ConnectorTransactionId, pub connector: String, - pub connector_refund_id: Option, + pub connector_refund_id: Option, pub external_reference_id: Option, pub refund_type: storage_enums::RefundType, pub total_amount: MinorUnit, @@ -51,6 +51,9 @@ pub struct Refund { pub merchant_connector_id: Option, pub charges: Option, pub organization_id: common_utils::id_type::OrganizationId, + pub connector_refund_data: Option, + pub connector_transaction_data: Option, + pub split_refunds: Option, } #[derive( @@ -71,9 +74,9 @@ pub struct RefundNew { pub merchant_id: common_utils::id_type::MerchantId, pub internal_reference_id: String, pub external_reference_id: Option, - pub connector_transaction_id: String, + pub connector_transaction_id: ConnectorTransactionId, pub connector: String, - pub connector_refund_id: Option, + pub connector_refund_id: Option, pub refund_type: storage_enums::RefundType, pub total_amount: MinorUnit, pub currency: storage_enums::Currency, @@ -94,17 +97,21 @@ pub struct RefundNew { pub merchant_connector_id: Option, pub charges: Option, pub organization_id: common_utils::id_type::OrganizationId, + pub connector_refund_data: Option, + pub connector_transaction_data: Option, + pub split_refunds: Option, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub enum RefundUpdate { Update { - connector_refund_id: String, + connector_refund_id: ConnectorTransactionId, refund_status: storage_enums::RefundStatus, sent_to_gateway: bool, refund_error_message: Option, refund_arn: String, updated_by: String, + connector_refund_data: Option, }, MetadataAndReasonUpdate { metadata: Option, @@ -112,17 +119,19 @@ pub enum RefundUpdate { updated_by: String, }, StatusUpdate { - connector_refund_id: Option, + connector_refund_id: Option, sent_to_gateway: bool, refund_status: storage_enums::RefundStatus, updated_by: String, + connector_refund_data: Option, }, ErrorUpdate { refund_status: Option, refund_error_message: Option, refund_error_code: Option, updated_by: String, - connector_refund_id: Option, + connector_refund_id: Option, + connector_refund_data: Option, }, ManualUpdate { refund_status: Option, @@ -135,7 +144,7 @@ pub enum RefundUpdate { #[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)] #[diesel(table_name = refund)] pub struct RefundUpdateInternal { - connector_refund_id: Option, + connector_refund_id: Option, refund_status: Option, sent_to_gateway: Option, refund_error_message: Option, @@ -145,6 +154,7 @@ pub struct RefundUpdateInternal { refund_error_code: Option, updated_by: String, modified_at: PrimitiveDateTime, + connector_refund_data: Option, } impl RefundUpdateInternal { @@ -160,6 +170,7 @@ impl RefundUpdateInternal { refund_error_code: self.refund_error_code, updated_by: self.updated_by, modified_at: self.modified_at, + connector_refund_data: self.connector_refund_data, ..source } } @@ -175,6 +186,7 @@ impl From for RefundUpdateInternal { refund_error_message, refund_arn, updated_by, + connector_refund_data, } => Self { connector_refund_id: Some(connector_refund_id), refund_status: Some(refund_status), @@ -182,6 +194,7 @@ impl From for RefundUpdateInternal { refund_error_message, refund_arn: Some(refund_arn), updated_by, + connector_refund_data, metadata: None, refund_reason: None, refund_error_code: None, @@ -202,17 +215,20 @@ impl From for RefundUpdateInternal { refund_arn: None, refund_error_code: None, modified_at: common_utils::date_time::now(), + connector_refund_data: None, }, RefundUpdate::StatusUpdate { connector_refund_id, sent_to_gateway, refund_status, updated_by, + connector_refund_data, } => Self { connector_refund_id, sent_to_gateway: Some(sent_to_gateway), refund_status: Some(refund_status), updated_by, + connector_refund_data, refund_error_message: None, refund_arn: None, metadata: None, @@ -226,12 +242,14 @@ impl From for RefundUpdateInternal { refund_error_code, updated_by, connector_refund_id, + connector_refund_data, } => Self { refund_status, refund_error_message, refund_error_code, updated_by, connector_refund_id, + connector_refund_data, sent_to_gateway: None, refund_arn: None, metadata: None, @@ -254,6 +272,7 @@ impl From for RefundUpdateInternal { metadata: None, refund_reason: None, modified_at: common_utils::date_time::now(), + connector_refund_data: None, }, } } @@ -272,6 +291,7 @@ impl RefundUpdate { refund_error_code, updated_by, modified_at: _, + connector_refund_data, } = self.into(); Refund { connector_refund_id: connector_refund_id.or(source.connector_refund_id), @@ -284,6 +304,7 @@ impl RefundUpdate { refund_reason: refund_reason.or(source.refund_reason), updated_by, modified_at: common_utils::date_time::now(), + connector_refund_data: connector_refund_data.or(source.connector_refund_data), ..source } } @@ -292,11 +313,13 @@ impl RefundUpdate { #[derive(Debug, Eq, PartialEq, Deserialize, Serialize)] pub struct RefundCoreWorkflow { pub refund_internal_reference_id: String, - pub connector_transaction_id: String, + pub connector_transaction_id: ConnectorTransactionId, pub merchant_id: common_utils::id_type::MerchantId, pub payment_id: common_utils::id_type::PaymentId, + pub connector_transaction_data: Option, } +#[cfg(feature = "v1")] impl common_utils::events::ApiEventMetric for Refund { fn get_api_event_type(&self) -> Option { Some(common_utils::events::ApiEventsType::Refund { @@ -306,6 +329,37 @@ impl common_utils::events::ApiEventMetric for Refund { } } +impl ConnectorTransactionIdTrait for Refund { + fn get_optional_connector_refund_id(&self) -> Option<&String> { + match self + .connector_refund_id + .as_ref() + .map(|refund_id| refund_id.get_txn_id(self.connector_refund_data.as_ref())) + .transpose() + { + Ok(refund_id) => refund_id, + + // In case hashed data is missing from DB, use the hashed ID as connector transaction ID + Err(_) => self + .connector_refund_id + .as_ref() + .map(|txn_id| txn_id.get_id()), + } + } + + fn get_connector_transaction_id(&self) -> &String { + match self + .connector_transaction_id + .get_txn_id(self.connector_transaction_data.as_ref()) + { + Ok(txn_id) => txn_id, + + // In case hashed data is missing from DB, use the hashed ID as connector transaction ID + Err(_) => self.connector_transaction_id.get_id(), + } + } +} + mod tests { #[test] fn test_backwards_compatibility() { @@ -336,7 +390,8 @@ mod tests { "profile_id": null, "updated_by": "admin", "merchant_connector_id": null, - "charges": null + "charges": null, + "connector_transaction_data": null }"#; let deserialized = serde_json::from_str::(serialized_refund); diff --git a/crates/diesel_models/src/relay.rs b/crates/diesel_models/src/relay.rs new file mode 100644 index 000000000000..153e06ab17f5 --- /dev/null +++ b/crates/diesel_models/src/relay.rs @@ -0,0 +1,77 @@ +use common_utils::pii; +use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; +use time::PrimitiveDateTime; + +use crate::{enums as storage_enums, schema::relay}; + +#[derive( + Clone, + Debug, + Eq, + Identifiable, + Queryable, + Selectable, + PartialEq, + serde::Serialize, + serde::Deserialize, +)] +#[diesel(table_name = relay)] +pub struct Relay { + pub id: common_utils::id_type::RelayId, + pub connector_resource_id: String, + pub connector_id: common_utils::id_type::MerchantConnectorAccountId, + pub profile_id: common_utils::id_type::ProfileId, + pub merchant_id: common_utils::id_type::MerchantId, + pub relay_type: storage_enums::RelayType, + pub request_data: Option, + pub status: storage_enums::RelayStatus, + pub connector_reference_id: Option, + pub error_code: Option, + pub error_message: Option, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub created_at: PrimitiveDateTime, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub modified_at: PrimitiveDateTime, + pub response_data: Option, +} + +#[derive( + Clone, + Debug, + Eq, + PartialEq, + Insertable, + router_derive::DebugAsDisplay, + serde::Serialize, + serde::Deserialize, + router_derive::Setter, +)] +#[diesel(table_name = relay)] +pub struct RelayNew { + pub id: common_utils::id_type::RelayId, + pub connector_resource_id: String, + pub connector_id: common_utils::id_type::MerchantConnectorAccountId, + pub profile_id: common_utils::id_type::ProfileId, + pub merchant_id: common_utils::id_type::MerchantId, + pub relay_type: storage_enums::RelayType, + pub request_data: Option, + pub status: storage_enums::RelayStatus, + pub connector_reference_id: Option, + pub error_code: Option, + pub error_message: Option, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub created_at: PrimitiveDateTime, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub modified_at: PrimitiveDateTime, + pub response_data: Option, +} + +#[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)] +#[diesel(table_name = relay)] +pub struct RelayUpdateInternal { + pub connector_reference_id: Option, + pub status: Option, + pub error_code: Option, + pub error_message: Option, + pub modified_at: PrimitiveDateTime, +} diff --git a/crates/diesel_models/src/reverse_lookup.rs b/crates/diesel_models/src/reverse_lookup.rs index 87fca3440784..974851f966a1 100644 --- a/crates/diesel_models/src/reverse_lookup.rs +++ b/crates/diesel_models/src/reverse_lookup.rs @@ -2,7 +2,6 @@ use diesel::{Identifiable, Insertable, Queryable, Selectable}; use crate::schema::reverse_lookup; -/// /// This reverse lookup table basically looks up id's and get result_id that you want. This is /// useful for KV where you can't lookup without key #[derive( diff --git a/crates/diesel_models/src/role.rs b/crates/diesel_models/src/role.rs index 3fb64e645d60..8199bd3979ce 100644 --- a/crates/diesel_models/src/role.rs +++ b/crates/diesel_models/src/role.rs @@ -18,7 +18,7 @@ pub struct Role { pub created_by: String, pub last_modified_at: PrimitiveDateTime, pub last_modified_by: String, - pub entity_type: Option, + pub entity_type: enums::EntityType, } #[derive(router_derive::Setter, Clone, Debug, Insertable, router_derive::DebugAsDisplay)] @@ -35,7 +35,7 @@ pub struct RoleNew { pub created_by: String, pub last_modified_at: PrimitiveDateTime, pub last_modified_by: String, - pub entity_type: Option, + pub entity_type: enums::EntityType, } #[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)] diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index a9e0b54260c1..95bb714cb711 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -120,6 +120,7 @@ diesel::table! { directory_server_id -> Nullable, #[max_length = 64] acquirer_country_code -> Nullable, + service_details -> Nullable, } } @@ -213,6 +214,8 @@ diesel::table! { is_network_tokenization_enabled -> Bool, is_auto_retries_enabled -> Nullable, max_auto_retries_enabled -> Nullable, + is_click_to_pay_enabled -> Bool, + authentication_product_ids -> Nullable, } } @@ -248,6 +251,8 @@ diesel::table! { capture_sequence -> Int2, #[max_length = 128] connector_response_reference_id -> Nullable, + #[max_length = 512] + connector_capture_data -> Nullable, } } @@ -382,6 +387,38 @@ diesel::table! { dispute_amount -> Int8, #[max_length = 32] organization_id -> Varchar, + dispute_currency -> Nullable, + } +} + +diesel::table! { + use diesel::sql_types::*; + use crate::enums::diesel_exports::*; + + dynamic_routing_stats (attempt_id, merchant_id) { + #[max_length = 64] + payment_id -> Varchar, + #[max_length = 64] + attempt_id -> Varchar, + #[max_length = 64] + merchant_id -> Varchar, + #[max_length = 64] + profile_id -> Varchar, + amount -> Int8, + #[max_length = 64] + success_based_routing_connector -> Varchar, + #[max_length = 64] + payment_connector -> Varchar, + currency -> Nullable, + #[max_length = 64] + payment_method -> Nullable, + capture_method -> Nullable, + authentication_type -> Nullable, + payment_status -> AttemptStatus, + conclusive_classification -> SuccessBasedRoutingConclusiveState, + created_at -> Timestamp, + #[max_length = 64] + payment_method_type -> Nullable, } } @@ -505,6 +542,8 @@ diesel::table! { unified_code -> Nullable, #[max_length = 1024] unified_message -> Nullable, + #[max_length = 64] + error_category -> Nullable, } } @@ -678,6 +717,7 @@ diesel::table! { payment_link_config -> Nullable, pm_collect_link_config -> Nullable, version -> ApiVersion, + is_platform_account -> Bool, } } @@ -846,6 +886,9 @@ diesel::table! { card_network -> Nullable, shipping_cost -> Nullable, order_tax_amount -> Nullable, + #[max_length = 512] + connector_transaction_data -> Nullable, + connector_mandate_detail -> Nullable, } } @@ -926,6 +969,10 @@ diesel::table! { organization_id -> Varchar, tax_details -> Nullable, skip_external_tax_calculation -> Nullable, + psd2_sca_exemption_type -> Nullable, + split_payments -> Nullable, + #[max_length = 64] + platform_merchant_id -> Nullable, } } @@ -1183,6 +1230,40 @@ diesel::table! { charges -> Nullable, #[max_length = 32] organization_id -> Varchar, + #[max_length = 512] + connector_refund_data -> Nullable, + #[max_length = 512] + connector_transaction_data -> Nullable, + split_refunds -> Nullable, + } +} + +diesel::table! { + use diesel::sql_types::*; + use crate::enums::diesel_exports::*; + + relay (id) { + #[max_length = 64] + id -> Varchar, + #[max_length = 128] + connector_resource_id -> Varchar, + #[max_length = 64] + connector_id -> Varchar, + #[max_length = 64] + profile_id -> Varchar, + #[max_length = 64] + merchant_id -> Varchar, + relay_type -> RelayType, + request_data -> Nullable, + status -> RelayStatus, + #[max_length = 128] + connector_reference_id -> Nullable, + #[max_length = 64] + error_code -> Nullable, + error_message -> Nullable, + created_at -> Timestamp, + modified_at -> Timestamp, + response_data -> Nullable, } } @@ -1226,7 +1307,7 @@ diesel::table! { #[max_length = 64] last_modified_by -> Varchar, #[max_length = 64] - entity_type -> Nullable, + entity_type -> Varchar, } } @@ -1253,6 +1334,39 @@ diesel::table! { } } +diesel::table! { + use diesel::sql_types::*; + use crate::enums::diesel_exports::*; + + themes (theme_id) { + #[max_length = 64] + theme_id -> Varchar, + #[max_length = 64] + tenant_id -> Varchar, + #[max_length = 64] + org_id -> Nullable, + #[max_length = 64] + merchant_id -> Nullable, + #[max_length = 64] + profile_id -> Nullable, + created_at -> Timestamp, + last_modified_at -> Timestamp, + #[max_length = 64] + entity_type -> Varchar, + #[max_length = 64] + theme_name -> Varchar, + #[max_length = 64] + email_primary_color -> Varchar, + #[max_length = 64] + email_foreground_color -> Varchar, + #[max_length = 64] + email_background_color -> Varchar, + #[max_length = 64] + email_entity_name -> Varchar, + email_entity_logo_url -> Text, + } +} + diesel::table! { use diesel::sql_types::*; use crate::enums::diesel_exports::*; @@ -1334,6 +1448,8 @@ diesel::table! { #[max_length = 64] entity_type -> Nullable, version -> UserRoleVersion, + #[max_length = 64] + tenant_id -> Varchar, } } @@ -1374,6 +1490,7 @@ diesel::allow_tables_to_appear_in_same_query!( customers, dashboard_metadata, dispute, + dynamic_routing_stats, events, file_metadata, fraud_check, @@ -1394,9 +1511,11 @@ diesel::allow_tables_to_appear_in_same_query!( payouts, process_tracker, refund, + relay, reverse_lookup, roles, routing_algorithm, + themes, unified_translations, user_authentication_methods, user_key_store, diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index 53f568f18c46..8bbb4baf9d74 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -121,6 +121,7 @@ diesel::table! { directory_server_id -> Nullable, #[max_length = 64] acquirer_country_code -> Nullable, + service_details -> Nullable, } } @@ -213,6 +214,7 @@ diesel::table! { #[max_length = 64] payout_routing_algorithm_id -> Nullable, default_fallback_routing -> Nullable, + should_collect_cvv_during_payment -> Bool, #[max_length = 64] id -> Varchar, version -> ApiVersion, @@ -220,6 +222,8 @@ diesel::table! { is_network_tokenization_enabled -> Bool, is_auto_retries_enabled -> Nullable, max_auto_retries_enabled -> Nullable, + is_click_to_pay_enabled -> Bool, + authentication_product_ids -> Nullable, } } @@ -255,6 +259,8 @@ diesel::table! { capture_sequence -> Int2, #[max_length = 128] connector_response_reference_id -> Nullable, + #[max_length = 512] + connector_capture_data -> Nullable, } } @@ -393,6 +399,38 @@ diesel::table! { dispute_amount -> Int8, #[max_length = 32] organization_id -> Varchar, + dispute_currency -> Nullable, + } +} + +diesel::table! { + use diesel::sql_types::*; + use crate::enums::diesel_exports::*; + + dynamic_routing_stats (attempt_id, merchant_id) { + #[max_length = 64] + payment_id -> Varchar, + #[max_length = 64] + attempt_id -> Varchar, + #[max_length = 64] + merchant_id -> Varchar, + #[max_length = 64] + profile_id -> Varchar, + amount -> Int8, + #[max_length = 64] + success_based_routing_connector -> Varchar, + #[max_length = 64] + payment_connector -> Varchar, + currency -> Nullable, + #[max_length = 64] + payment_method -> Nullable, + capture_method -> Nullable, + authentication_type -> Nullable, + payment_status -> AttemptStatus, + conclusive_classification -> SuccessBasedRoutingConclusiveState, + created_at -> Timestamp, + #[max_length = 64] + payment_method_type -> Nullable, } } @@ -516,6 +554,8 @@ diesel::table! { unified_code -> Nullable, #[max_length = 1024] unified_message -> Nullable, + #[max_length = 64] + error_category -> Nullable, } } @@ -668,6 +708,7 @@ diesel::table! { version -> ApiVersion, #[max_length = 64] id -> Varchar, + is_platform_account -> Bool, } } @@ -682,7 +723,7 @@ diesel::table! { connector_name -> Varchar, connector_account_details -> Bytea, disabled -> Nullable, - payment_methods_enabled -> Nullable>>, + payment_methods_enabled -> Nullable>>, connector_type -> ConnectorType, metadata -> Nullable, #[max_length = 255] @@ -747,8 +788,7 @@ diesel::table! { surcharge_amount -> Nullable, #[max_length = 64] payment_method_id -> Nullable, - confirm -> Bool, - authentication_type -> Nullable, + authentication_type -> AuthenticationType, created_at -> Timestamp, modified_at -> Timestamp, last_synced -> Nullable, @@ -774,13 +814,12 @@ diesel::table! { updated_by -> Varchar, #[max_length = 32] merchant_connector_id -> Nullable, - authentication_data -> Nullable, encoded_data -> Nullable, #[max_length = 255] unified_code -> Nullable, #[max_length = 1024] unified_message -> Nullable, - net_amount -> Nullable, + net_amount -> Int8, external_three_ds_authentication_attempted -> Nullable, #[max_length = 64] authentication_connector -> Nullable, @@ -789,8 +828,6 @@ diesel::table! { #[max_length = 64] fingerprint_id -> Nullable, #[max_length = 64] - payment_method_billing_address_id -> Nullable, - #[max_length = 64] charge_id -> Nullable, #[max_length = 64] client_source -> Nullable, @@ -803,20 +840,25 @@ diesel::table! { organization_id -> Varchar, #[max_length = 32] card_network -> Nullable, - payment_method_type_v2 -> Nullable, + payment_method_type_v2 -> Varchar, #[max_length = 128] connector_payment_id -> Nullable, #[max_length = 64] - payment_method_subtype -> Nullable, + payment_method_subtype -> Varchar, routing_result -> Nullable, authentication_applied -> Nullable, #[max_length = 128] external_reference_id -> Nullable, tax_on_surcharge -> Nullable, + payment_method_billing_address -> Nullable, + redirection_data -> Nullable, + #[max_length = 512] + connector_payment_data -> Nullable, #[max_length = 64] id -> Varchar, shipping_cost -> Nullable, order_tax_amount -> Nullable, + connector_mandate_detail -> Nullable, } } @@ -845,7 +887,7 @@ diesel::table! { #[max_length = 128] client_secret -> Varchar, #[max_length = 64] - active_attempt_id -> Varchar, + active_attempt_id -> Nullable, order_details -> Nullable>>, allowed_payment_method_types -> Nullable, connector_metadata -> Nullable, @@ -860,7 +902,7 @@ diesel::table! { surcharge_applicable -> Nullable, request_incremental_authorization -> Nullable, authorization_count -> Nullable, - session_expiry -> Nullable, + session_expiry -> Timestamp, request_external_three_ds_authentication -> Nullable, frm_metadata -> Nullable, customer_details -> Nullable, @@ -890,6 +932,10 @@ diesel::table! { payment_link_config -> Nullable, #[max_length = 64] id -> Varchar, + psd2_sca_exemption_type -> Nullable, + split_payments -> Nullable, + #[max_length = 64] + platform_merchant_id -> Nullable, } } @@ -934,10 +980,6 @@ diesel::table! { merchant_id -> Varchar, created_at -> Timestamp, last_modified -> Timestamp, - payment_method -> Nullable, - #[max_length = 64] - payment_method_type -> Nullable, - metadata -> Nullable, payment_method_data -> Nullable, #[max_length = 64] locker_id -> Nullable, @@ -956,6 +998,10 @@ diesel::table! { #[max_length = 64] locker_fingerprint_id -> Nullable, #[max_length = 64] + payment_method_type_v2 -> Nullable, + #[max_length = 64] + payment_method_subtype -> Nullable, + #[max_length = 64] id -> Varchar, version -> ApiVersion, #[max_length = 128] @@ -1130,6 +1176,40 @@ diesel::table! { charges -> Nullable, #[max_length = 32] organization_id -> Varchar, + #[max_length = 512] + connector_refund_data -> Nullable, + #[max_length = 512] + connector_transaction_data -> Nullable, + split_refunds -> Nullable, + } +} + +diesel::table! { + use diesel::sql_types::*; + use crate::enums::diesel_exports::*; + + relay (id) { + #[max_length = 64] + id -> Varchar, + #[max_length = 128] + connector_resource_id -> Varchar, + #[max_length = 64] + connector_id -> Varchar, + #[max_length = 64] + profile_id -> Varchar, + #[max_length = 64] + merchant_id -> Varchar, + relay_type -> RelayType, + request_data -> Nullable, + status -> RelayStatus, + #[max_length = 128] + connector_reference_id -> Nullable, + #[max_length = 64] + error_code -> Nullable, + error_message -> Nullable, + created_at -> Timestamp, + modified_at -> Timestamp, + response_data -> Nullable, } } @@ -1174,7 +1254,7 @@ diesel::table! { #[max_length = 64] last_modified_by -> Varchar, #[max_length = 64] - entity_type -> Nullable, + entity_type -> Varchar, } } @@ -1201,6 +1281,39 @@ diesel::table! { } } +diesel::table! { + use diesel::sql_types::*; + use crate::enums::diesel_exports::*; + + themes (theme_id) { + #[max_length = 64] + theme_id -> Varchar, + #[max_length = 64] + tenant_id -> Varchar, + #[max_length = 64] + org_id -> Nullable, + #[max_length = 64] + merchant_id -> Nullable, + #[max_length = 64] + profile_id -> Nullable, + created_at -> Timestamp, + last_modified_at -> Timestamp, + #[max_length = 64] + entity_type -> Varchar, + #[max_length = 64] + theme_name -> Varchar, + #[max_length = 64] + email_primary_color -> Varchar, + #[max_length = 64] + email_foreground_color -> Varchar, + #[max_length = 64] + email_background_color -> Varchar, + #[max_length = 64] + email_entity_name -> Varchar, + email_entity_logo_url -> Text, + } +} + diesel::table! { use diesel::sql_types::*; use crate::enums::diesel_exports::*; @@ -1282,6 +1395,8 @@ diesel::table! { #[max_length = 64] entity_type -> Nullable, version -> UserRoleVersion, + #[max_length = 64] + tenant_id -> Varchar, } } @@ -1323,6 +1438,7 @@ diesel::allow_tables_to_appear_in_same_query!( customers, dashboard_metadata, dispute, + dynamic_routing_stats, events, file_metadata, fraud_check, @@ -1343,9 +1459,11 @@ diesel::allow_tables_to_appear_in_same_query!( payouts, process_tracker, refund, + relay, reverse_lookup, roles, routing_algorithm, + themes, unified_translations, user_authentication_methods, user_key_store, diff --git a/crates/diesel_models/src/services/logger.rs b/crates/diesel_models/src/services/logger.rs deleted file mode 100644 index 9c1b20c9d280..000000000000 --- a/crates/diesel_models/src/services/logger.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! -//! Logger of the system. -//! - -pub use crate::env::logger::*; diff --git a/crates/diesel_models/src/types.rs b/crates/diesel_models/src/types.rs new file mode 100644 index 000000000000..7806a7c6e1b8 --- /dev/null +++ b/crates/diesel_models/src/types.rs @@ -0,0 +1,108 @@ +use common_utils::{hashing::HashedString, pii, types::MinorUnit}; +use diesel::{ + sql_types::{Json, Jsonb}, + AsExpression, FromSqlRow, +}; +use masking::{Secret, WithType}; +use serde::{Deserialize, Serialize}; +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, FromSqlRow, AsExpression)] +#[diesel(sql_type = Jsonb)] +pub struct OrderDetailsWithAmount { + /// Name of the product that is being purchased + pub product_name: String, + /// The quantity of the product to be purchased + pub quantity: u16, + /// the amount per quantity of product + pub amount: MinorUnit, + // Does the order includes shipping + pub requires_shipping: Option, + /// The image URL of the product + pub product_img_link: Option, + /// ID of the product that is being purchased + pub product_id: Option, + /// Category of the product that is being purchased + pub category: Option, + /// Sub category of the product that is being purchased + pub sub_category: Option, + /// Brand of the product that is being purchased + pub brand: Option, + /// Type of the product that is being purchased + pub product_type: Option, + /// The tax code for the product + pub product_tax_code: Option, + /// tax rate applicable to the product + pub tax_rate: Option, + /// total tax amount applicable to the product + pub total_tax_amount: Option, +} + +impl masking::SerializableSecret for OrderDetailsWithAmount {} + +common_utils::impl_to_sql_from_sql_json!(OrderDetailsWithAmount); + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, FromSqlRow, AsExpression)] +#[diesel(sql_type = Json)] +pub struct FeatureMetadata { + /// Redirection response coming in request as metadata field only for redirection scenarios + pub redirect_response: Option, + // TODO: Convert this to hashedstrings to avoid PII sensitive data + /// Additional tags to be used for global search + pub search_tags: Option>>, + /// Recurring payment details required for apple pay Merchant Token + pub apple_pay_recurring_details: Option, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, FromSqlRow, AsExpression)] +#[diesel(sql_type = Json)] +pub struct ApplePayRecurringDetails { + /// A description of the recurring payment that Apple Pay displays to the user in the payment sheet + pub payment_description: String, + /// The regular billing cycle for the recurring payment, including start and end dates, an interval, and an interval count + pub regular_billing: ApplePayRegularBillingDetails, + /// A localized billing agreement that the payment sheet displays to the user before the user authorizes the payment + pub billing_agreement: Option, + /// A URL to a web page where the user can update or delete the payment method for the recurring payment + pub management_url: common_utils::types::Url, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, FromSqlRow, AsExpression)] +#[diesel(sql_type = Json)] +pub struct ApplePayRegularBillingDetails { + /// The label that Apple Pay displays to the user in the payment sheet with the recurring details + pub label: String, + /// The date of the first payment + #[serde(with = "common_utils::custom_serde::iso8601::option")] + pub recurring_payment_start_date: Option, + /// The date of the final payment + #[serde(with = "common_utils::custom_serde::iso8601::option")] + pub recurring_payment_end_date: Option, + /// The amount of time — in calendar units, such as day, month, or year — that represents a fraction of the total payment interval + pub recurring_payment_interval_unit: Option, + /// The number of interval units that make up the total payment interval + pub recurring_payment_interval_count: Option, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, FromSqlRow, AsExpression)] +#[diesel(sql_type = Json)] +#[serde(rename_all = "snake_case")] +pub enum RecurringPaymentIntervalUnit { + Year, + Month, + Day, + Hour, + Minute, +} + +common_utils::impl_to_sql_from_sql_json!(ApplePayRecurringDetails); +common_utils::impl_to_sql_from_sql_json!(ApplePayRegularBillingDetails); +common_utils::impl_to_sql_from_sql_json!(RecurringPaymentIntervalUnit); + +common_utils::impl_to_sql_from_sql_json!(FeatureMetadata); + +#[derive(Default, Debug, Eq, PartialEq, Deserialize, Serialize, Clone)] +pub struct RedirectResponse { + pub param: Option>, + pub json_payload: Option, +} +impl masking::SerializableSecret for RedirectResponse {} +common_utils::impl_to_sql_from_sql_json!(RedirectResponse); diff --git a/crates/diesel_models/src/user.rs b/crates/diesel_models/src/user.rs index 9f7b77dc5d68..cf584c09b129 100644 --- a/crates/diesel_models/src/user.rs +++ b/crates/diesel_models/src/user.rs @@ -6,8 +6,9 @@ use time::PrimitiveDateTime; use crate::{diesel_impl::OptionalDieselArray, enums::TotpStatus, schema::users}; pub mod dashboard_metadata; - pub mod sample_data; +pub mod theme; + #[derive(Clone, Debug, Identifiable, Queryable, Selectable)] #[diesel(table_name = users, primary_key(user_id), check_for_backend(diesel::pg::Pg))] pub struct User { diff --git a/crates/diesel_models/src/user/sample_data.rs b/crates/diesel_models/src/user/sample_data.rs index a354e4a02abe..cfc9e1c4c8ed 100644 --- a/crates/diesel_models/src/user/sample_data.rs +++ b/crates/diesel_models/src/user/sample_data.rs @@ -2,7 +2,7 @@ use common_enums::{ AttemptStatus, AuthenticationType, CaptureMethod, Currency, PaymentExperience, PaymentMethod, PaymentMethodType, }; -use common_utils::types::MinorUnit; +use common_utils::types::{ConnectorTransactionId, MinorUnit}; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -12,125 +12,123 @@ use crate::schema::payment_attempt; use crate::schema_v2::payment_attempt; use crate::{ enums::{MandateDataType, MandateDetails}, - PaymentAttemptNew, + ConnectorMandateReferenceId, PaymentAttemptNew, }; -#[cfg(feature = "v2")] -#[derive( - Clone, Debug, diesel::Insertable, router_derive::DebugAsDisplay, Serialize, Deserialize, -)] -#[diesel(table_name = payment_attempt)] -pub struct PaymentAttemptBatchNew { - pub payment_id: common_utils::id_type::PaymentId, - pub merchant_id: common_utils::id_type::MerchantId, - pub status: AttemptStatus, - pub error_message: Option, - pub surcharge_amount: Option, - pub tax_on_surcharge: Option, - pub payment_method_id: Option, - pub confirm: bool, - pub authentication_type: Option, - #[serde(with = "common_utils::custom_serde::iso8601")] - pub created_at: PrimitiveDateTime, - #[serde(with = "common_utils::custom_serde::iso8601")] - pub modified_at: PrimitiveDateTime, - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] - pub last_synced: Option, - pub cancellation_reason: Option, - pub browser_info: Option, - pub payment_token: Option, - pub error_code: Option, - pub connector_metadata: Option, - pub payment_experience: Option, - pub payment_method_data: Option, - pub preprocessing_step_id: Option, - pub error_reason: Option, - pub connector_response_reference_id: Option, - pub multiple_capture_count: Option, - pub amount_capturable: i64, - pub updated_by: String, - pub merchant_connector_id: Option, - pub authentication_data: Option, - pub encoded_data: Option, - pub unified_code: Option, - pub unified_message: Option, - pub net_amount: Option, - pub external_three_ds_authentication_attempted: Option, - pub authentication_connector: Option, - pub authentication_id: Option, - pub payment_method_billing_address_id: Option, - pub fingerprint_id: Option, - pub charge_id: Option, - pub client_source: Option, - pub client_version: Option, - pub customer_acceptance: Option, - pub profile_id: common_utils::id_type::ProfileId, - pub organization_id: common_utils::id_type::OrganizationId, -} +// #[cfg(feature = "v2")] +// #[derive( +// Clone, Debug, diesel::Insertable, router_derive::DebugAsDisplay, Serialize, Deserialize, +// )] +// #[diesel(table_name = payment_attempt)] +// pub struct PaymentAttemptBatchNew { +// pub payment_id: common_utils::id_type::PaymentId, +// pub merchant_id: common_utils::id_type::MerchantId, +// pub status: AttemptStatus, +// pub error_message: Option, +// pub surcharge_amount: Option, +// pub tax_on_surcharge: Option, +// pub payment_method_id: Option, +// pub authentication_type: Option, +// #[serde(with = "common_utils::custom_serde::iso8601")] +// pub created_at: PrimitiveDateTime, +// #[serde(with = "common_utils::custom_serde::iso8601")] +// pub modified_at: PrimitiveDateTime, +// #[serde(default, with = "common_utils::custom_serde::iso8601::option")] +// pub last_synced: Option, +// pub cancellation_reason: Option, +// pub browser_info: Option, +// pub payment_token: Option, +// pub error_code: Option, +// pub connector_metadata: Option, +// pub payment_experience: Option, +// pub payment_method_data: Option, +// pub preprocessing_step_id: Option, +// pub error_reason: Option, +// pub connector_response_reference_id: Option, +// pub multiple_capture_count: Option, +// pub amount_capturable: i64, +// pub updated_by: String, +// pub merchant_connector_id: Option, +// pub authentication_data: Option, +// pub encoded_data: Option, +// pub unified_code: Option, +// pub unified_message: Option, +// pub net_amount: Option, +// pub external_three_ds_authentication_attempted: Option, +// pub authentication_connector: Option, +// pub authentication_id: Option, +// pub fingerprint_id: Option, +// pub charge_id: Option, +// pub client_source: Option, +// pub client_version: Option, +// pub customer_acceptance: Option, +// pub profile_id: common_utils::id_type::ProfileId, +// pub organization_id: common_utils::id_type::OrganizationId, +// } -#[cfg(feature = "v2")] -#[allow(dead_code)] -impl PaymentAttemptBatchNew { - // Used to verify compatibility with PaymentAttemptTable - fn convert_into_normal_attempt_insert(self) -> PaymentAttemptNew { - // PaymentAttemptNew { - // payment_id: self.payment_id, - // merchant_id: self.merchant_id, - // status: self.status, - // error_message: self.error_message, - // surcharge_amount: self.surcharge_amount, - // tax_amount: self.tax_amount, - // payment_method_id: self.payment_method_id, - // confirm: self.confirm, - // authentication_type: self.authentication_type, - // created_at: self.created_at, - // modified_at: self.modified_at, - // last_synced: self.last_synced, - // cancellation_reason: self.cancellation_reason, - // browser_info: self.browser_info, - // payment_token: self.payment_token, - // error_code: self.error_code, - // connector_metadata: self.connector_metadata, - // payment_experience: self.payment_experience, - // card_network: self - // .payment_method_data - // .as_ref() - // .and_then(|data| data.as_object()) - // .and_then(|card| card.get("card")) - // .and_then(|v| v.as_object()) - // .and_then(|v| v.get("card_network")) - // .and_then(|network| network.as_str()) - // .map(|network| network.to_string()), - // payment_method_data: self.payment_method_data, - // straight_through_algorithm: self.straight_through_algorithm, - // preprocessing_step_id: self.preprocessing_step_id, - // 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, - // updated_by: self.updated_by, - // merchant_connector_id: self.merchant_connector_id, - // authentication_data: self.authentication_data, - // encoded_data: self.encoded_data, - // unified_code: self.unified_code, - // unified_message: self.unified_message, - // net_amount: self.net_amount, - // external_three_ds_authentication_attempted: self - // .external_three_ds_authentication_attempted, - // authentication_connector: self.authentication_connector, - // authentication_id: self.authentication_id, - // 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, - // customer_acceptance: self.customer_acceptance, - // profile_id: self.profile_id, - // organization_id: self.organization_id, - // } - todo!() - } -} +// #[cfg(feature = "v2")] +// #[allow(dead_code)] +// impl PaymentAttemptBatchNew { +// // Used to verify compatibility with PaymentAttemptTable +// fn convert_into_normal_attempt_insert(self) -> PaymentAttemptNew { +// // PaymentAttemptNew { +// // payment_id: self.payment_id, +// // merchant_id: self.merchant_id, +// // status: self.status, +// // error_message: self.error_message, +// // surcharge_amount: self.surcharge_amount, +// // tax_amount: self.tax_amount, +// // payment_method_id: self.payment_method_id, +// // confirm: self.confirm, +// // authentication_type: self.authentication_type, +// // created_at: self.created_at, +// // modified_at: self.modified_at, +// // last_synced: self.last_synced, +// // cancellation_reason: self.cancellation_reason, +// // browser_info: self.browser_info, +// // payment_token: self.payment_token, +// // error_code: self.error_code, +// // connector_metadata: self.connector_metadata, +// // payment_experience: self.payment_experience, +// // card_network: self +// // .payment_method_data +// // .as_ref() +// // .and_then(|data| data.as_object()) +// // .and_then(|card| card.get("card")) +// // .and_then(|v| v.as_object()) +// // .and_then(|v| v.get("card_network")) +// // .and_then(|network| network.as_str()) +// // .map(|network| network.to_string()), +// // payment_method_data: self.payment_method_data, +// // straight_through_algorithm: self.straight_through_algorithm, +// // preprocessing_step_id: self.preprocessing_step_id, +// // 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, +// // updated_by: self.updated_by, +// // merchant_connector_id: self.merchant_connector_id, +// // authentication_data: self.authentication_data, +// // encoded_data: self.encoded_data, +// // unified_code: self.unified_code, +// // unified_message: self.unified_message, +// // net_amount: self.net_amount, +// // external_three_ds_authentication_attempted: self +// // .external_three_ds_authentication_attempted, +// // authentication_connector: self.authentication_connector, +// // authentication_id: self.authentication_id, +// // 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, +// // customer_acceptance: self.customer_acceptance, +// // profile_id: self.profile_id, +// // organization_id: self.organization_id, +// // } +// todo!() +// } +// } #[cfg(feature = "v1")] #[derive( @@ -179,7 +177,7 @@ pub struct PaymentAttemptBatchNew { pub mandate_details: Option, pub error_reason: Option, pub connector_response_reference_id: Option, - pub connector_transaction_id: Option, + pub connector_transaction_id: Option, pub multiple_capture_count: Option, pub amount_capturable: MinorUnit, pub updated_by: String, @@ -203,6 +201,8 @@ pub struct PaymentAttemptBatchNew { pub organization_id: common_utils::id_type::OrganizationId, pub shipping_cost: Option, pub order_tax_amount: Option, + pub connector_transaction_data: Option, + pub connector_mandate_detail: Option, } #[cfg(feature = "v1")] @@ -281,6 +281,7 @@ impl PaymentAttemptBatchNew { organization_id: self.organization_id, shipping_cost: self.shipping_cost, order_tax_amount: self.order_tax_amount, + connector_mandate_detail: self.connector_mandate_detail, } } } diff --git a/crates/diesel_models/src/user/theme.rs b/crates/diesel_models/src/user/theme.rs new file mode 100644 index 000000000000..46cc90a45eca --- /dev/null +++ b/crates/diesel_models/src/user/theme.rs @@ -0,0 +1,87 @@ +use common_enums::EntityType; +use common_utils::{ + date_time, id_type, + types::theme::{EmailThemeConfig, ThemeLineage}, +}; +use diesel::{Identifiable, Insertable, Queryable, Selectable}; +use time::PrimitiveDateTime; + +use crate::schema::themes; + +#[derive(Clone, Debug, Identifiable, Queryable, Selectable)] +#[diesel(table_name = themes, primary_key(theme_id), check_for_backend(diesel::pg::Pg))] +pub struct Theme { + pub theme_id: String, + pub tenant_id: id_type::TenantId, + pub org_id: Option, + pub merchant_id: Option, + pub profile_id: Option, + pub created_at: PrimitiveDateTime, + pub last_modified_at: PrimitiveDateTime, + pub entity_type: EntityType, + pub theme_name: String, + pub email_primary_color: String, + pub email_foreground_color: String, + pub email_background_color: String, + pub email_entity_name: String, + pub email_entity_logo_url: String, +} + +#[derive(Clone, Debug, Insertable, router_derive::DebugAsDisplay)] +#[diesel(table_name = themes)] +pub struct ThemeNew { + pub theme_id: String, + pub tenant_id: id_type::TenantId, + pub org_id: Option, + pub merchant_id: Option, + pub profile_id: Option, + pub created_at: PrimitiveDateTime, + pub last_modified_at: PrimitiveDateTime, + pub entity_type: EntityType, + pub theme_name: String, + pub email_primary_color: String, + pub email_foreground_color: String, + pub email_background_color: String, + pub email_entity_name: String, + pub email_entity_logo_url: String, +} + +impl ThemeNew { + pub fn new( + theme_id: String, + theme_name: String, + lineage: ThemeLineage, + email_config: EmailThemeConfig, + ) -> Self { + let now = date_time::now(); + + Self { + theme_id, + theme_name, + tenant_id: lineage.tenant_id().to_owned(), + org_id: lineage.org_id().cloned(), + merchant_id: lineage.merchant_id().cloned(), + profile_id: lineage.profile_id().cloned(), + entity_type: lineage.entity_type(), + created_at: now, + last_modified_at: now, + email_primary_color: email_config.primary_color, + email_foreground_color: email_config.foreground_color, + email_background_color: email_config.background_color, + email_entity_name: email_config.entity_name, + email_entity_logo_url: email_config.entity_logo_url, + } + } +} + +impl Theme { + pub fn email_config(&self) -> EmailThemeConfig { + EmailThemeConfig { + primary_color: self.email_primary_color.clone(), + foreground_color: self.email_foreground_color.clone(), + background_color: self.email_background_color.clone(), + entity_name: self.email_entity_name.clone(), + entity_logo_url: self.email_entity_logo_url.clone(), + } + } +} diff --git a/crates/diesel_models/src/user_role.rs b/crates/diesel_models/src/user_role.rs index 71caa41deac4..08449685b291 100644 --- a/crates/diesel_models/src/user_role.rs +++ b/crates/diesel_models/src/user_role.rs @@ -24,20 +24,24 @@ pub struct UserRole { pub entity_id: Option, pub entity_type: Option, pub version: enums::UserRoleVersion, + pub tenant_id: id_type::TenantId, } impl UserRole { pub fn get_entity_id_and_type(&self) -> Option<(String, EntityType)> { - match (self.version, self.role_id.as_str()) { - (enums::UserRoleVersion::V1, consts::ROLE_ID_ORGANIZATION_ADMIN) => { + match (self.version, self.entity_type, self.role_id.as_str()) { + (enums::UserRoleVersion::V1, None, consts::ROLE_ID_ORGANIZATION_ADMIN) => { let org_id = self.org_id.clone()?.get_string_repr().to_string(); Some((org_id, EntityType::Organization)) } - (enums::UserRoleVersion::V1, _) => { + (enums::UserRoleVersion::V1, None, _) => { let merchant_id = self.merchant_id.clone()?.get_string_repr().to_string(); Some((merchant_id, EntityType::Merchant)) } - (enums::UserRoleVersion::V2, _) => self.entity_id.clone().zip(self.entity_type), + (enums::UserRoleVersion::V1, Some(_), _) => { + self.entity_id.clone().zip(self.entity_type) + } + (enums::UserRoleVersion::V2, _, _) => self.entity_id.clone().zip(self.entity_type), } } } @@ -87,6 +91,7 @@ pub struct UserRoleNew { pub entity_id: Option, pub entity_type: Option, pub version: enums::UserRoleVersion, + pub tenant_id: id_type::TenantId, } #[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)] diff --git a/crates/drainer/Cargo.toml b/crates/drainer/Cargo.toml index 5cb01dc8fb63..ce7301a03059 100644 --- a/crates/drainer/Cargo.toml +++ b/crates/drainer/Cargo.toml @@ -10,7 +10,7 @@ license.workspace = true [features] release = ["vergen", "external_services/aws_kms"] vergen = ["router_env/vergen"] -v1 = ["diesel_models/v1", "hyperswitch_interfaces/v1"] +v1 = ["diesel_models/v1", "hyperswitch_interfaces/v1", "common_utils/v1"] [dependencies] actix-web = "4.5.1" diff --git a/crates/drainer/src/handler.rs b/crates/drainer/src/handler.rs index d8a8bff5afc2..74984b03fbd2 100644 --- a/crates/drainer/src/handler.rs +++ b/crates/drainer/src/handler.rs @@ -3,6 +3,7 @@ use std::{ sync::{atomic, Arc}, }; +use common_utils::id_type; use router_env::tracing::Instrument; use tokio::{ sync::{mpsc, oneshot}, @@ -34,12 +35,15 @@ pub struct HandlerInner { loop_interval: Duration, active_tasks: Arc, conf: DrainerSettings, - stores: HashMap>, + stores: HashMap>, running: Arc, } impl Handler { - pub fn from_conf(conf: DrainerSettings, stores: HashMap>) -> Self { + pub fn from_conf( + conf: DrainerSettings, + stores: HashMap>, + ) -> Self { let shutdown_interval = Duration::from_millis(conf.shutdown_interval.into()); let loop_interval = Duration::from_millis(conf.loop_interval.into()); @@ -70,7 +74,7 @@ impl Handler { let jobs_picked = Arc::new(atomic::AtomicU8::new(0)); while self.running.load(atomic::Ordering::SeqCst) { - metrics::DRAINER_HEALTH.add(&metrics::CONTEXT, 1, &[]); + metrics::DRAINER_HEALTH.add(1, &[]); for store in self.stores.values() { if store.is_stream_available(stream_index).await { let _task_handle = tokio::spawn( @@ -99,7 +103,7 @@ impl Handler { pub(crate) async fn shutdown_listener(&self, mut rx: mpsc::Receiver<()>) { while let Some(_c) = rx.recv().await { logger::info!("Awaiting shutdown!"); - metrics::SHUTDOWN_SIGNAL_RECEIVED.add(&metrics::CONTEXT, 1, &[]); + metrics::SHUTDOWN_SIGNAL_RECEIVED.add(1, &[]); let shutdown_started = time::Instant::now(); rx.close(); @@ -108,9 +112,9 @@ impl Handler { time::sleep(self.shutdown_interval).await; } logger::info!("Terminating drainer"); - metrics::SUCCESSFUL_SHUTDOWN.add(&metrics::CONTEXT, 1, &[]); + metrics::SUCCESSFUL_SHUTDOWN.add(1, &[]); let shutdown_ended = shutdown_started.elapsed().as_secs_f64() * 1000f64; - metrics::CLEANUP_TIME.record(&metrics::CONTEXT, shutdown_ended, &[]); + metrics::CLEANUP_TIME.record(shutdown_ended, &[]); self.close(); } logger::info!( @@ -213,7 +217,7 @@ async fn drainer( if let redis_interface::errors::RedisError::StreamEmptyOrNotAvailable = redis_err.current_context() { - metrics::STREAM_EMPTY.add(&metrics::CONTEXT, 1, &[]); + metrics::STREAM_EMPTY.add(1, &[]); return Ok(()); } else { return Err(error); @@ -232,12 +236,8 @@ async fn drainer( let read_count = entries.len(); metrics::JOBS_PICKED_PER_STREAM.add( - &metrics::CONTEXT, u64::try_from(read_count).unwrap_or(u64::MIN), - &[metrics::KeyValue { - key: "stream".into(), - value: stream_name.to_string().into(), - }], + router_env::metric_attributes!(("stream", stream_name.to_owned())), ); let session_id = common_utils::generate_id_with_default_len("drainer_session"); @@ -250,12 +250,8 @@ async fn drainer( Err(err) => { logger::error!(operation = "deserialization", err=?err); metrics::STREAM_PARSE_FAIL.add( - &metrics::CONTEXT, 1, - &[metrics::KeyValue { - key: "operation".into(), - value: "deserialization".into(), - }], + router_env::metric_attributes!(("operation", "deserialization")), ); // break from the loop in case of a deser error diff --git a/crates/drainer/src/health_check.rs b/crates/drainer/src/health_check.rs index 48d5f3119056..2ca2c1cc79c1 100644 --- a/crates/drainer/src/health_check.rs +++ b/crates/drainer/src/health_check.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, sync::Arc}; use actix_web::{web, Scope}; use async_bb8_diesel::{AsyncConnection, AsyncRunQueryDsl}; -use common_utils::errors::CustomResult; +use common_utils::{errors::CustomResult, id_type}; use diesel_models::{Config, ConfigNew}; use error_stack::ResultExt; use router_env::{instrument, logger, tracing}; @@ -20,7 +20,7 @@ pub const TEST_STREAM_DATA: &[(&str, &str)] = &[("data", "sample_data")]; pub struct Health; impl Health { - pub fn server(conf: Settings, stores: HashMap>) -> Scope { + pub fn server(conf: Settings, stores: HashMap>) -> Scope { web::scope("health") .app_data(web::Data::new(conf)) .app_data(web::Data::new(stores)) diff --git a/crates/drainer/src/lib.rs b/crates/drainer/src/lib.rs index 5b67640663c6..6eb8c505e15a 100644 --- a/crates/drainer/src/lib.rs +++ b/crates/drainer/src/lib.rs @@ -14,7 +14,7 @@ use std::{collections::HashMap, sync::Arc}; mod secrets_transformers; use actix_web::dev::Server; -use common_utils::signals::get_allowed_signals; +use common_utils::{id_type, signals::get_allowed_signals}; use diesel_models::kv; use error_stack::ResultExt; use hyperswitch_interfaces::secrets_interface::secret_state::RawSecret; @@ -31,7 +31,7 @@ use crate::{ }; pub async fn start_drainer( - stores: HashMap>, + stores: HashMap>, conf: DrainerSettings, ) -> errors::DrainerResult<()> { let drainer_handler = handler::Handler::from_conf(conf, stores); @@ -62,7 +62,7 @@ pub async fn start_drainer( pub async fn start_web_server( conf: Settings, - stores: HashMap>, + stores: HashMap>, ) -> Result { let server = conf.server.clone(); let web_server = actix_web::HttpServer::new(move || { diff --git a/crates/drainer/src/logger.rs b/crates/drainer/src/logger.rs index b23b0ab2675d..8044b0462d94 100644 --- a/crates/drainer/src/logger.rs +++ b/crates/drainer/src/logger.rs @@ -1,2 +1,2 @@ #[doc(inline)] -pub use router_env::*; +pub use router_env::{debug, error, info, warn}; diff --git a/crates/drainer/src/main.rs b/crates/drainer/src/main.rs index c5a4a39f95d4..91ec191bf9f2 100644 --- a/crates/drainer/src/main.rs +++ b/crates/drainer/src/main.rs @@ -1,8 +1,6 @@ use std::collections::HashMap; -use drainer::{ - errors::DrainerResult, logger::logger, services, settings, start_drainer, start_web_server, -}; +use drainer::{errors::DrainerResult, logger, services, settings, start_drainer, start_web_server}; use router_env::tracing::Instrument; #[tokio::main] diff --git a/crates/drainer/src/metrics.rs b/crates/drainer/src/metrics.rs index 750f23bc73b5..13fb31f7c500 100644 --- a/crates/drainer/src/metrics.rs +++ b/crates/drainer/src/metrics.rs @@ -1,9 +1,5 @@ -pub use router_env::opentelemetry::KeyValue; -use router_env::{ - counter_metric, global_meter, histogram_metric, histogram_metric_i64, metrics_context, -}; +use router_env::{counter_metric, global_meter, histogram_metric_f64, histogram_metric_u64}; -metrics_context!(CONTEXT); global_meter!(DRAINER_METER, "DRAINER"); counter_metric!(JOBS_PICKED_PER_STREAM, DRAINER_METER); @@ -17,8 +13,8 @@ counter_metric!(STREAM_EMPTY, DRAINER_METER); counter_metric!(STREAM_PARSE_FAIL, DRAINER_METER); counter_metric!(DRAINER_HEALTH, DRAINER_METER); -histogram_metric!(QUERY_EXECUTION_TIME, DRAINER_METER); // Time in (ms) milliseconds -histogram_metric!(REDIS_STREAM_READ_TIME, DRAINER_METER); // Time in (ms) milliseconds -histogram_metric!(REDIS_STREAM_TRIM_TIME, DRAINER_METER); // Time in (ms) milliseconds -histogram_metric!(CLEANUP_TIME, DRAINER_METER); // Time in (ms) milliseconds -histogram_metric_i64!(DRAINER_DELAY_SECONDS, DRAINER_METER); // Time in (s) seconds +histogram_metric_f64!(QUERY_EXECUTION_TIME, DRAINER_METER); // Time in (ms) milliseconds +histogram_metric_f64!(REDIS_STREAM_READ_TIME, DRAINER_METER); // Time in (ms) milliseconds +histogram_metric_f64!(REDIS_STREAM_TRIM_TIME, DRAINER_METER); // Time in (ms) milliseconds +histogram_metric_f64!(CLEANUP_TIME, DRAINER_METER); // Time in (ms) milliseconds +histogram_metric_u64!(DRAINER_DELAY_SECONDS, DRAINER_METER); // Time in (s) seconds diff --git a/crates/drainer/src/query.rs b/crates/drainer/src/query.rs index a1e04fb6d0f1..ec6b271aa9da 100644 --- a/crates/drainer/src/query.rs +++ b/crates/drainer/src/query.rs @@ -25,32 +25,23 @@ impl ExecuteQuery for kv::DBOperation { let operation = self.operation(); let table = self.table(); - let tags: &[metrics::KeyValue] = &[ - metrics::KeyValue { - key: "operation".into(), - value: operation.into(), - }, - metrics::KeyValue { - key: "table".into(), - value: table.into(), - }, - ]; + let tags = router_env::metric_attributes!(("operation", operation), ("table", table)); let (result, execution_time) = Box::pin(common_utils::date_time::time_it(|| self.execute(&conn))).await; push_drainer_delay(pushed_at, operation, table, tags); - metrics::QUERY_EXECUTION_TIME.record(&metrics::CONTEXT, execution_time, tags); + metrics::QUERY_EXECUTION_TIME.record(execution_time, tags); match result { Ok(result) => { logger::info!(operation = operation, table = table, ?result); - metrics::SUCCESSFUL_QUERY_EXECUTION.add(&metrics::CONTEXT, 1, tags); + metrics::SUCCESSFUL_QUERY_EXECUTION.add(1, tags); Ok(()) } Err(err) => { logger::error!(operation = operation, table = table, ?err); - metrics::ERRORS_WHILE_QUERY_EXECUTION.add(&metrics::CONTEXT, 1, tags); + metrics::ERRORS_WHILE_QUERY_EXECUTION.add(1, tags); Err(err) } } @@ -58,15 +49,25 @@ impl ExecuteQuery for kv::DBOperation { } #[inline(always)] -fn push_drainer_delay(pushed_at: i64, operation: &str, table: &str, tags: &[metrics::KeyValue]) { +fn push_drainer_delay( + pushed_at: i64, + operation: &str, + table: &str, + tags: &[router_env::opentelemetry::KeyValue], +) { let drained_at = common_utils::date_time::now_unix_timestamp(); let delay = drained_at - pushed_at; - logger::debug!( - operation = operation, - table = table, - delay = format!("{delay} secs") - ); + logger::debug!(operation, table, delay = format!("{delay} secs")); - metrics::DRAINER_DELAY_SECONDS.record(&metrics::CONTEXT, delay, tags); + match u64::try_from(delay) { + Ok(delay) => metrics::DRAINER_DELAY_SECONDS.record(delay, tags), + Err(error) => logger::error!( + pushed_at, + drained_at, + delay, + ?error, + "Invalid drainer delay" + ), + } } diff --git a/crates/drainer/src/services.rs b/crates/drainer/src/services.rs index 55ffe0c4e7fa..87057ebeff2d 100644 --- a/crates/drainer/src/services.rs +++ b/crates/drainer/src/services.rs @@ -29,7 +29,6 @@ impl Store { /// /// Panics if there is a failure while obtaining the HashiCorp client using the provided configuration. /// This panic indicates a critical failure in setting up external services, and the application cannot proceed without a valid HashiCorp client. - /// pub async fn new(config: &crate::Settings, test_transaction: bool, tenant: &Tenant) -> Self { let redis_conn = crate::connection::redis_connection(config).await; Self { diff --git a/crates/drainer/src/settings.rs b/crates/drainer/src/settings.rs index 052267395041..9b6c88b34665 100644 --- a/crates/drainer/src/settings.rs +++ b/crates/drainer/src/settings.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, path::PathBuf, sync::Arc}; -use common_utils::{ext_traits::ConfigExt, DbConnectionParams}; +use common_utils::{ext_traits::ConfigExt, id_type, DbConnectionParams}; use config::{Environment, File}; use external_services::managers::{ encryption_management::EncryptionManagementConfig, secrets_management::SecretsManagementConfig, @@ -122,24 +122,59 @@ pub struct Multitenancy { pub tenants: TenantConfig, } impl Multitenancy { - pub fn get_tenants(&self) -> &HashMap { + pub fn get_tenants(&self) -> &HashMap { &self.tenants.0 } - pub fn get_tenant_names(&self) -> Vec { - self.tenants.0.keys().cloned().collect() + pub fn get_tenant_ids(&self) -> Vec { + self.tenants + .0 + .values() + .map(|tenant| tenant.tenant_id.clone()) + .collect() } - pub fn get_tenant(&self, tenant_id: &str) -> Option<&Tenant> { + pub fn get_tenant(&self, tenant_id: &id_type::TenantId) -> Option<&Tenant> { self.tenants.0.get(tenant_id) } } -#[derive(Debug, Deserialize, Clone, Default)] -#[serde(transparent)] -pub struct TenantConfig(pub HashMap); +#[derive(Debug, Clone, Default)] +pub struct TenantConfig(pub HashMap); + +impl<'de> Deserialize<'de> for TenantConfig { + fn deserialize>(deserializer: D) -> Result { + #[derive(Deserialize)] + struct Inner { + base_url: String, + schema: String, + redis_key_prefix: String, + clickhouse_database: String, + } -#[derive(Debug, Deserialize, Clone, Default)] + let hashmap = >::deserialize(deserializer)?; + + Ok(Self( + hashmap + .into_iter() + .map(|(key, value)| { + ( + key.clone(), + Tenant { + tenant_id: key, + base_url: value.base_url, + schema: value.schema, + redis_key_prefix: value.redis_key_prefix, + clickhouse_database: value.clickhouse_database, + }, + ) + }) + .collect(), + )) + } +} + +#[derive(Debug, Deserialize, Clone)] pub struct Tenant { - pub name: String, + pub tenant_id: id_type::TenantId, pub base_url: String, pub schema: String, pub redis_key_prefix: String, diff --git a/crates/drainer/src/stream.rs b/crates/drainer/src/stream.rs index 319fc2b0e1d3..f5b41c536727 100644 --- a/crates/drainer/src/stream.rs +++ b/crates/drainer/src/stream.rs @@ -69,9 +69,8 @@ impl Store { .await; metrics::REDIS_STREAM_READ_TIME.record( - &metrics::CONTEXT, execution_time, - &[metrics::KeyValue::new("stream", stream_name.to_owned())], + router_env::metric_attributes!(("stream", stream_name.to_owned())), ); Ok(output?) @@ -104,9 +103,8 @@ impl Store { .await; metrics::REDIS_STREAM_TRIM_TIME.record( - &metrics::CONTEXT, execution_time, - &[metrics::KeyValue::new("stream", stream_name.to_owned())], + router_env::metric_attributes!(("stream", stream_name.to_owned())), ); // adding 1 because we are deleting the given id too diff --git a/crates/drainer/src/utils.rs b/crates/drainer/src/utils.rs index c8c6e312f14b..72f12c60492a 100644 --- a/crates/drainer/src/utils.rs +++ b/crates/drainer/src/utils.rs @@ -63,8 +63,8 @@ pub async fn increment_stream_index( ) -> u8 { if index == total_streams - 1 { match jobs_picked.load(atomic::Ordering::SeqCst) { - 0 => metrics::CYCLES_COMPLETED_UNSUCCESSFULLY.add(&metrics::CONTEXT, 1, &[]), - _ => metrics::CYCLES_COMPLETED_SUCCESSFULLY.add(&metrics::CONTEXT, 1, &[]), + 0 => metrics::CYCLES_COMPLETED_UNSUCCESSFULLY.add(1, &[]), + _ => metrics::CYCLES_COMPLETED_SUCCESSFULLY.add(1, &[]), } jobs_picked.store(0, atomic::Ordering::SeqCst); 0 diff --git a/crates/euclid/src/dssa/analyzer.rs b/crates/euclid/src/dssa/analyzer.rs index a81e7be351fb..7aeb850e1bb0 100644 --- a/crates/euclid/src/dssa/analyzer.rs +++ b/crates/euclid/src/dssa/analyzer.rs @@ -87,7 +87,7 @@ pub fn analyze_exhaustive_negations( .cloned() .unwrap_or_default() .iter() - .cloned() + .copied() .cloned() .collect(), }; @@ -121,12 +121,12 @@ fn analyze_negated_assertions( value: (*val).clone(), assertion_metadata: assertion_metadata .get(*val) - .cloned() + .copied() .cloned() .unwrap_or_default(), negation_metadata: negation_metadata .get(*val) - .cloned() + .copied() .cloned() .unwrap_or_default(), }; diff --git a/crates/euclid/src/dssa/graph.rs b/crates/euclid/src/dssa/graph.rs index 13e115153829..7ef9bb244d98 100644 --- a/crates/euclid/src/dssa/graph.rs +++ b/crates/euclid/src/dssa/graph.rs @@ -70,6 +70,7 @@ impl cgraph::NodeViz for dir::DirValue { Self::CardRedirectType(crt) => crt.to_string(), Self::RealTimePaymentType(rtpt) => rtpt.to_string(), Self::OpenBankingType(ob) => ob.to_string(), + Self::MobilePaymentType(mpt) => mpt.to_string(), } } } @@ -420,7 +421,7 @@ impl CgraphExt for cgraph::ConstraintGraph { for (key, negation_set) in keywise_negation { let all_metadata = keywise_metadata.remove(&key).unwrap_or_default(); - let first_metadata = all_metadata.first().cloned().cloned().unwrap_or_default(); + let first_metadata = all_metadata.first().copied().cloned().unwrap_or_default(); self.key_analysis(key.clone(), analysis_ctx, memo, cycle_map, domains) .map_err(|e| AnalysisError::assertion_from_graph_error(&first_metadata, e))?; diff --git a/crates/euclid/src/dssa/types.rs b/crates/euclid/src/dssa/types.rs index df54de2dd998..f8340c315097 100644 --- a/crates/euclid/src/dssa/types.rs +++ b/crates/euclid/src/dssa/types.rs @@ -18,7 +18,7 @@ pub enum CtxValueKind<'a> { Negation(&'a [dir::DirValue]), } -impl<'a> CtxValueKind<'a> { +impl CtxValueKind<'_> { pub fn get_assertion(&self) -> Option<&dir::DirValue> { if let Self::Assertion(val) = self { Some(val) diff --git a/crates/euclid/src/frontend/ast.rs b/crates/euclid/src/frontend/ast.rs index 5a7b88acfbc2..7c75ad000bf8 100644 --- a/crates/euclid/src/frontend/ast.rs +++ b/crates/euclid/src/frontend/ast.rs @@ -135,7 +135,6 @@ pub struct IfStatement { /// } /// } /// ``` - #[derive(Clone, Debug, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] #[aliases(RuleConnectorSelection = Rule)] diff --git a/crates/euclid/src/frontend/ast/lowering.rs b/crates/euclid/src/frontend/ast/lowering.rs index 8a8ba695e9fa..ec629358d5fe 100644 --- a/crates/euclid/src/frontend/ast/lowering.rs +++ b/crates/euclid/src/frontend/ast/lowering.rs @@ -25,7 +25,6 @@ use crate::{ /// This serves for the purpose were we have the DirKey as an explicit Enum type and value as one /// of the member of the same Enum. /// So particularly it lowers a predefined Enum from DirKey to an Enum of DirValue. - macro_rules! lower_enum { ($key:ident, $value:ident) => { match $value { @@ -70,7 +69,6 @@ macro_rules! lower_enum { /// This is for the cases in which there are numerical values involved and they are lowered /// accordingly on basis of the supplied key, currently payment_amount is the only key having this /// use case - macro_rules! lower_number { ($key:ident, $value:ident, $comp:ident) => { match $value { @@ -117,7 +115,6 @@ macro_rules! lower_number { /// /// This serves for the purpose were we have the DirKey as Card_bin and value as an arbitrary string /// So particularly it lowers an arbitrary value to a predefined key. - macro_rules! lower_str { ($key:ident, $value:ident $(, $validation_closure:expr)?) => { match $value { @@ -155,7 +152,6 @@ macro_rules! lower_metadata { /// by throwing required errors for comparisons that can't be performed for a certain value type /// for example /// can't have greater/less than operations on enum types - fn lower_comparison_inner( comp: ast::Comparison, ) -> Result, AnalysisErrorType> { @@ -274,6 +270,8 @@ fn lower_comparison_inner( dir::DirKeyKind::CardRedirectType => lower_enum!(CardRedirectType, value), + dir::DirKeyKind::MobilePaymentType => lower_enum!(MobilePaymentType, value), + dir::DirKeyKind::RealTimePaymentType => lower_enum!(RealTimePaymentType, value), dir::DirKeyKind::CardBin => { diff --git a/crates/euclid/src/frontend/ast/parser.rs b/crates/euclid/src/frontend/ast/parser.rs index 0c586e178e69..63a0ea08b8b6 100644 --- a/crates/euclid/src/frontend/ast/parser.rs +++ b/crates/euclid/src/frontend/ast/parser.rs @@ -51,9 +51,9 @@ impl EuclidParsable for DummyOutput { )(input) } } -pub fn skip_ws<'a, F, O>(inner: F) -> impl FnMut(&'a str) -> ParseResult<&str, O> +pub fn skip_ws<'a, F, O>(inner: F) -> impl FnMut(&'a str) -> ParseResult<&'a str, O> where - F: FnMut(&'a str) -> ParseResult<&str, O> + 'a, + F: FnMut(&'a str) -> ParseResult<&'a str, O> + 'a, { sequence::preceded(pchar::multispace0, inner) } diff --git a/crates/euclid/src/frontend/dir.rs b/crates/euclid/src/frontend/dir.rs index 9a7134e708a7..1bfe1fa16b7c 100644 --- a/crates/euclid/src/frontend/dir.rs +++ b/crates/euclid/src/frontend/dir.rs @@ -278,6 +278,13 @@ pub enum DirKeyKind { props(Category = "Payment Method Types") )] OpenBankingType, + #[serde(rename = "mobile_payment")] + #[strum( + serialize = "mobile_payment", + detailed_message = "Supported types of mobile payment method", + props(Category = "Payment Method Types") + )] + MobilePaymentType, } pub trait EuclidDirFilter: Sized @@ -327,6 +334,7 @@ impl DirKeyKind { Self::CardRedirectType => types::DataType::EnumVariant, Self::RealTimePaymentType => types::DataType::EnumVariant, Self::OpenBankingType => types::DataType::EnumVariant, + Self::MobilePaymentType => types::DataType::EnumVariant, } } pub fn get_value_set(&self) -> Option> { @@ -459,6 +467,11 @@ impl DirKeyKind { .map(DirValue::OpenBankingType) .collect(), ), + Self::MobilePaymentType => Some( + enums::MobilePaymentType::iter() + .map(DirValue::MobilePaymentType) + .collect(), + ), } } } @@ -528,6 +541,8 @@ pub enum DirValue { RealTimePaymentType(enums::RealTimePaymentType), #[serde(rename = "open_banking")] OpenBankingType(enums::OpenBankingType), + #[serde(rename = "mobile_payment")] + MobilePaymentType(enums::MobilePaymentType), } impl DirValue { @@ -563,6 +578,7 @@ impl DirValue { Self::GiftCardType(_) => (DirKeyKind::GiftCardType, None), Self::RealTimePaymentType(_) => (DirKeyKind::RealTimePaymentType, None), Self::OpenBankingType(_) => (DirKeyKind::OpenBankingType, None), + Self::MobilePaymentType(_) => (DirKeyKind::MobilePaymentType, None), }; DirKey::new(kind, data) @@ -599,6 +615,7 @@ impl DirValue { Self::CardRedirectType(_) => None, Self::RealTimePaymentType(_) => None, Self::OpenBankingType(_) => None, + Self::MobilePaymentType(_) => None, } } diff --git a/crates/euclid/src/frontend/dir/enums.rs b/crates/euclid/src/frontend/dir/enums.rs index 0d2f959c702c..6c96070159be 100644 --- a/crates/euclid/src/frontend/dir/enums.rs +++ b/crates/euclid/src/frontend/dir/enums.rs @@ -91,6 +91,7 @@ pub enum WalletType { Cashapp, Venmo, Mifinity, + Paze, } #[derive( @@ -254,6 +255,25 @@ pub enum CardRedirectType { CardRedirect, } +#[derive( + Clone, + Debug, + Hash, + PartialEq, + Eq, + strum::Display, + strum::VariantNames, + strum::EnumIter, + strum::EnumString, + serde::Serialize, + serde::Deserialize, +)] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum MobilePaymentType { + DirectCarrierBilling, +} + #[derive( Clone, Debug, @@ -371,3 +391,4 @@ collect_variants!(GiftCardType); collect_variants!(BankTransferType); collect_variants!(CardRedirectType); collect_variants!(OpenBankingType); +collect_variants!(MobilePaymentType); diff --git a/crates/euclid/src/frontend/dir/lowering.rs b/crates/euclid/src/frontend/dir/lowering.rs index 33d368e96961..07ff2c4364ec 100644 --- a/crates/euclid/src/frontend/dir/lowering.rs +++ b/crates/euclid/src/frontend/dir/lowering.rs @@ -58,6 +58,7 @@ impl From for global_enums::PaymentMethodType { enums::WalletType::Cashapp => Self::Cashapp, enums::WalletType::Venmo => Self::Venmo, enums::WalletType::Mifinity => Self::Mifinity, + enums::WalletType::Paze => Self::Paze, } } } @@ -143,6 +144,14 @@ impl From for global_enums::PaymentMethodType { } } +impl From for global_enums::PaymentMethodType { + fn from(value: enums::MobilePaymentType) -> Self { + match value { + enums::MobilePaymentType::DirectCarrierBilling => Self::DirectCarrierBilling, + } + } +} + impl From for global_enums::PaymentMethodType { fn from(value: enums::BankRedirectType) -> Self { match value { @@ -247,6 +256,7 @@ fn lower_value(dir_value: dir::DirValue) -> Result EuclidValue::BusinessLabel(bl), dir::DirValue::SetupFutureUsage(sfu) => EuclidValue::SetupFutureUsage(sfu), dir::DirValue::OpenBankingType(ob) => EuclidValue::PaymentMethodType(ob.into()), + dir::DirValue::MobilePaymentType(mp) => EuclidValue::PaymentMethodType(mp.into()), }) } diff --git a/crates/euclid/src/frontend/dir/transformers.rs b/crates/euclid/src/frontend/dir/transformers.rs index 3e35bcd391c4..914a94449183 100644 --- a/crates/euclid/src/frontend/dir/transformers.rs +++ b/crates/euclid/src/frontend/dir/transformers.rs @@ -39,6 +39,7 @@ impl IntoDirValue for (global_enums::PaymentMethodType, global_enums::PaymentMet | global_enums::PaymentMethod::Upi | global_enums::PaymentMethod::Voucher | global_enums::PaymentMethod::OpenBanking + | global_enums::PaymentMethod::MobilePayment | global_enums::PaymentMethod::GiftCard => Err(AnalysisErrorType::NotSupported), }, global_enums::PaymentMethodType::Bacs => match self.1 { @@ -55,6 +56,7 @@ impl IntoDirValue for (global_enums::PaymentMethodType, global_enums::PaymentMet | global_enums::PaymentMethod::Upi | global_enums::PaymentMethod::Voucher | global_enums::PaymentMethod::OpenBanking + | global_enums::PaymentMethod::MobilePayment | global_enums::PaymentMethod::GiftCard => Err(AnalysisErrorType::NotSupported), }, global_enums::PaymentMethodType::Becs => Ok(dirval!(BankDebitType = Becs)), @@ -72,6 +74,7 @@ impl IntoDirValue for (global_enums::PaymentMethodType, global_enums::PaymentMet | global_enums::PaymentMethod::Upi | global_enums::PaymentMethod::Voucher | global_enums::PaymentMethod::OpenBanking + | global_enums::PaymentMethod::MobilePayment | global_enums::PaymentMethod::GiftCard => Err(AnalysisErrorType::NotSupported), }, global_enums::PaymentMethodType::AliPay => Ok(dirval!(WalletType = AliPay)), @@ -188,6 +191,10 @@ impl IntoDirValue for (global_enums::PaymentMethodType, global_enums::PaymentMet global_enums::PaymentMethodType::OpenBankingPIS => { Ok(dirval!(OpenBankingType = OpenBankingPIS)) } + global_enums::PaymentMethodType::Paze => Ok(dirval!(WalletType = Paze)), + global_enums::PaymentMethodType::DirectCarrierBilling => { + Ok(dirval!(MobilePaymentType = DirectCarrierBilling)) + } } } } diff --git a/crates/euclid_wasm/Cargo.toml b/crates/euclid_wasm/Cargo.toml index f8c97699d06d..19c425e4c01d 100644 --- a/crates/euclid_wasm/Cargo.toml +++ b/crates/euclid_wasm/Cargo.toml @@ -14,10 +14,10 @@ default = ["payouts"] release = ["payouts"] dummy_connector = ["kgraph_utils/dummy_connector", "connector_configs/dummy_connector"] production = ["connector_configs/production"] -development = ["connector_configs/development"] sandbox = ["connector_configs/sandbox"] payouts = ["api_models/payouts", "euclid/payouts"] -v1 = ["api_models/v1"] +v1 = ["api_models/v1", "kgraph_utils/v1"] +v2 = [] [dependencies] api_models = { version = "0.1.0", path = "../api_models", package = "api_models" } diff --git a/crates/euclid_wasm/src/lib.rs b/crates/euclid_wasm/src/lib.rs index 4f2e9aa3156a..b299ced68b01 100644 --- a/crates/euclid_wasm/src/lib.rs +++ b/crates/euclid_wasm/src/lib.rs @@ -7,8 +7,8 @@ use std::{ }; use api_models::{ - admin as admin_api, conditional_configs::ConditionalConfigs, enums as api_model_enums, - routing::ConnectorSelection, surcharge_decision_configs::SurchargeDecisionConfigs, + conditional_configs::ConditionalConfigs, enums as api_model_enums, routing::ConnectorSelection, + surcharge_decision_configs::SurchargeDecisionConfigs, }; use common_enums::RoutableConnectors; use connector_configs::{ @@ -20,7 +20,7 @@ use currency_conversion::{ }; use euclid::{ backend::{inputs, interpreter::InterpreterBackend, EuclidBackend}, - dssa::{self, analyzer, graph::CgraphExt, state_machine, truth}, + dssa::{self, analyzer, graph::CgraphExt, state_machine}, frontend::{ ast, dir::{self, enums as dir_enums, EuclidDirFilter}, @@ -76,9 +76,11 @@ pub fn convert_forex_value(amount: i64, from_currency: JsValue, to_currency: JsV /// This function can be used by the frontend to provide the WASM with information about /// all the merchant's connector accounts. The input argument is a vector of all the merchant's /// connector accounts from the API. +#[cfg(feature = "v1")] #[wasm_bindgen(js_name = seedKnowledgeGraph)] pub fn seed_knowledge_graph(mcas: JsValue) -> JsResult { - let mcas: Vec = serde_wasm_bindgen::from_value(mcas)?; + let mcas: Vec = + serde_wasm_bindgen::from_value(mcas)?; let connectors: Vec = mcas .iter() .map(|mca| { @@ -95,9 +97,11 @@ pub fn seed_knowledge_graph(mcas: JsValue) -> JsResult { 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()?; + let analysis_graph = hyperswitch_constraint_graph::ConstraintGraph::combine( + &mca_graph, + &dssa::truth::ANALYSIS_GRAPH, + ) + .err_to_js()?; SEED_DATA .set(SeedData { @@ -262,6 +266,7 @@ pub fn get_variant_values(key: &str) -> Result { dir::DirKeyKind::BankDebitType => dir_enums::BankDebitType::VARIANTS, dir::DirKeyKind::RealTimePaymentType => dir_enums::RealTimePaymentType::VARIANTS, dir::DirKeyKind::OpenBankingType => dir_enums::OpenBankingType::VARIANTS, + dir::DirKeyKind::MobilePaymentType => dir_enums::MobilePaymentType::VARIANTS, dir::DirKeyKind::PaymentAmount | dir::DirKeyKind::Connector diff --git a/crates/events/src/lib.rs b/crates/events/src/lib.rs index 3d333ec54b9d..38a597e030ba 100644 --- a/crates/events/src/lib.rs +++ b/crates/events/src/lib.rs @@ -2,14 +2,12 @@ #![cfg_attr(docsrs, doc(cfg_hide(doc)))] #![warn(missing_docs)] -//! //! A generic event handler system. //! This library consists of 4 parts: //! Event Sink: A trait that defines how events are published. This could be a simple logger, a message queue, or a database. //! EventContext: A struct that holds the event sink and metadata about the event. This is used to create events. This can be used to add metadata to all events, such as the user who triggered the event. //! EventInfo: A trait that defines the metadata that is sent with the event. It works with the EventContext to add metadata to all events. //! Event: A trait that defines the event itself. This trait is used to define the data that is sent with the event and defines the event's type & identifier. -//! mod actix; diff --git a/crates/external_services/Cargo.toml b/crates/external_services/Cargo.toml index fd7391c13bca..e617dbe83519 100644 --- a/crates/external_services/Cargo.toml +++ b/crates/external_services/Cargo.toml @@ -12,8 +12,8 @@ aws_kms = ["dep:aws-config", "dep:aws-sdk-kms"] email = ["dep:aws-config"] aws_s3 = ["dep:aws-config", "dep:aws-sdk-s3"] hashicorp-vault = ["dep:vaultrs"] -v1 = ["hyperswitch_interfaces/v1"] -dynamic_routing = ["dep:prost", "dep:tonic", "dep:tonic-reflection", "dep:tonic-types", "dep:api_models", "tokio/macros", "tokio/rt-multi-thread" , "dep:tonic-build", "dep:router_env"] +v1 = ["hyperswitch_interfaces/v1", "common_utils/v1"] +dynamic_routing = ["dep:prost", "dep:tonic", "dep:tonic-reflection", "dep:tonic-types", "dep:api_models", "tokio/macros", "tokio/rt-multi-thread", "dep:tonic-build", "dep:router_env", "dep:hyper-util", "dep:http-body-util"] [dependencies] async-trait = "0.1.79" @@ -29,15 +29,18 @@ error-stack = "0.4.1" hex = "0.4.3" hyper = "0.14.28" hyper-proxy = "0.9.1" +lettre = "0.11.10" once_cell = "1.19.0" serde = { version = "1.0.197", features = ["derive"] } thiserror = "1.0.58" vaultrs = { version = "0.7.2", optional = true } prost = { version = "0.13", optional = true } -tokio = "1.37.0" +tokio = "1.37.0" tonic = { version = "0.12.2", optional = true } tonic-reflection = { version = "0.12.2", optional = true } tonic-types = { version = "0.12.2", optional = true } +hyper-util = { version = "0.1.9", optional = true } +http-body-util = { version = "0.1.2", optional = true } # First party crates @@ -49,7 +52,7 @@ api_models = { version = "0.1.0", path = "../api_models", optional = true } [build-dependencies] -tonic-build = { version = "0.12" , optional = true } +tonic-build = { version = "0.12", optional = true } router_env = { version = "0.1.0", path = "../router_env", default-features = false, optional = true } [lints] diff --git a/crates/external_services/build.rs b/crates/external_services/build.rs index 605ef6997157..b3fa1a8fed2a 100644 --- a/crates/external_services/build.rs +++ b/crates/external_services/build.rs @@ -3,11 +3,25 @@ fn main() -> Result<(), Box> { #[cfg(feature = "dynamic_routing")] { // Get the directory of the current crate - let proto_file = router_env::workspace_path() - .join("proto") - .join("success_rate.proto"); + + let proto_path = router_env::workspace_path().join("proto"); + let success_rate_proto_file = proto_path.join("success_rate.proto"); + let elimination_proto_file = proto_path.join("elimination_rate.proto"); + let health_check_proto_file = proto_path.join("health_check.proto"); + let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR")?); + // Compile the .proto file - tonic_build::compile_protos(proto_file).expect("Failed to compile success rate proto file"); + tonic_build::configure() + .out_dir(out_dir) + .compile( + &[ + success_rate_proto_file, + elimination_proto_file, + health_check_proto_file, + ], + &[proto_path], + ) + .expect("Failed to compile proto files"); } Ok(()) } diff --git a/crates/external_services/src/aws_kms/core.rs b/crates/external_services/src/aws_kms/core.rs index 8ffd631b819c..537e63655b5b 100644 --- a/crates/external_services/src/aws_kms/core.rs +++ b/crates/external_services/src/aws_kms/core.rs @@ -63,7 +63,7 @@ impl AwsKmsClient { // Logging using `Debug` representation of the error as the `Display` // representation does not hold sufficient information. logger::error!(aws_kms_sdk_error=?error, "Failed to AWS KMS decrypt data"); - metrics::AWS_KMS_DECRYPTION_FAILURES.add(&metrics::CONTEXT, 1, &[]); + metrics::AWS_KMS_DECRYPTION_FAILURES.add(1, &[]); }) .change_context(AwsKmsError::DecryptionFailed)?; @@ -75,7 +75,7 @@ impl AwsKmsClient { })?; let time_taken = start.elapsed(); - metrics::AWS_KMS_DECRYPT_TIME.record(&metrics::CONTEXT, time_taken.as_secs_f64(), &[]); + metrics::AWS_KMS_DECRYPT_TIME.record(time_taken.as_secs_f64(), &[]); Ok(output) } @@ -99,7 +99,7 @@ impl AwsKmsClient { // Logging using `Debug` representation of the error as the `Display` // representation does not hold sufficient information. logger::error!(aws_kms_sdk_error=?error, "Failed to AWS KMS encrypt data"); - metrics::AWS_KMS_ENCRYPTION_FAILURES.add(&metrics::CONTEXT, 1, &[]); + metrics::AWS_KMS_ENCRYPTION_FAILURES.add(1, &[]); }) .change_context(AwsKmsError::EncryptionFailed)?; @@ -108,7 +108,7 @@ impl AwsKmsClient { .ok_or(AwsKmsError::MissingCiphertextEncryptionOutput) .map(|blob| consts::BASE64_ENGINE.encode(blob.into_inner()))?; let time_taken = start.elapsed(); - metrics::AWS_KMS_ENCRYPT_TIME.record(&metrics::CONTEXT, time_taken.as_secs_f64(), &[]); + metrics::AWS_KMS_ENCRYPT_TIME.record(time_taken.as_secs_f64(), &[]); Ok(output) } diff --git a/crates/external_services/src/email.rs b/crates/external_services/src/email.rs index 5751de95c128..a98b03c0b545 100644 --- a/crates/external_services/src/email.rs +++ b/crates/external_services/src/email.rs @@ -7,6 +7,12 @@ use serde::Deserialize; /// Implementation of aws ses client pub mod ses; +/// Implementation of SMTP server client +pub mod smtp; + +/// Implementation of Email client when email support is disabled +pub mod no_email; + /// Custom Result type alias for Email operations. pub type EmailResult = CustomResult; @@ -41,6 +47,7 @@ pub trait EmailService: Sync + Send + dyn_clone::DynClone { /// Compose and send email using the email data async fn compose_and_send_email( &self, + base_url: &str, email_data: Box, proxy_url: Option<&String>, ) -> EmailResult<()>; @@ -54,10 +61,11 @@ where { async fn compose_and_send_email( &self, + base_url: &str, email_data: Box, proxy_url: Option<&String>, ) -> EmailResult<()> { - let email_data = email_data.get_email_data(); + let email_data = email_data.get_email_data(base_url); let email_data = email_data.await?; let EmailContents { @@ -107,21 +115,34 @@ pub struct EmailContents { #[async_trait::async_trait] pub trait EmailData { /// Get the email contents - async fn get_email_data(&self) -> CustomResult; + async fn get_email_data(&self, base_url: &str) -> CustomResult; } dyn_clone::clone_trait_object!(EmailClient); /// List of available email clients to choose from #[derive(Debug, Clone, Default, Deserialize)] -pub enum AvailableEmailClients { +#[serde(tag = "active_email_client")] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum EmailClientConfigs { #[default] + /// Default Email client to use when no client is specified + NoEmailClient, /// AWS ses email client - SES, + Ses { + /// AWS SES client configuration + aws_ses: ses::SESConfig, + }, + /// Other Simple SMTP server + Smtp { + /// SMTP server configuration + smtp: smtp::SmtpServerConfig, + }, } /// Struct that contains the settings required to construct an EmailClient. #[derive(Debug, Clone, Default, Deserialize)] +#[serde(default)] pub struct EmailSettings { /// The AWS region to send SES requests to. pub aws_region: String, @@ -132,11 +153,9 @@ pub struct EmailSettings { /// Sender email pub sender_email: String, - /// Configs related to AWS Simple Email Service - pub aws_ses: Option, - - /// The active email client to use - pub active_email_client: AvailableEmailClients, + #[serde(flatten)] + /// The client specific configurations + pub client_config: EmailClientConfigs, /// Recipient email for recon emails pub recon_recipient_email: pii::Email, @@ -145,6 +164,17 @@ pub struct EmailSettings { pub prod_intent_recipient_email: pii::Email, } +impl EmailSettings { + /// Validation for the Email client specific configurations + pub fn validate(&self) -> Result<(), &'static str> { + match &self.client_config { + EmailClientConfigs::Ses { ref aws_ses } => aws_ses.validate(), + EmailClientConfigs::Smtp { ref smtp } => smtp.validate(), + EmailClientConfigs::NoEmailClient => Ok(()), + } + } +} + /// Errors that could occur from EmailClient. #[derive(Debug, thiserror::Error)] pub enum EmailError { diff --git a/crates/external_services/src/email/no_email.rs b/crates/external_services/src/email/no_email.rs new file mode 100644 index 000000000000..6ec5d69e1ab9 --- /dev/null +++ b/crates/external_services/src/email/no_email.rs @@ -0,0 +1,37 @@ +use common_utils::{errors::CustomResult, pii}; +use router_env::logger; + +use crate::email::{EmailClient, EmailError, EmailResult, IntermediateString}; + +/// Client when email support is disabled +#[derive(Debug, Clone, Default, serde::Deserialize)] +pub struct NoEmailClient {} + +impl NoEmailClient { + /// Constructs a new client when email is disabled + pub async fn create() -> Self { + Self {} + } +} + +#[async_trait::async_trait] +impl EmailClient for NoEmailClient { + type RichText = String; + fn convert_to_rich_text( + &self, + intermediate_string: IntermediateString, + ) -> CustomResult { + Ok(intermediate_string.into_inner()) + } + + async fn send_email( + &self, + _recipient: pii::Email, + _subject: String, + _body: Self::RichText, + _proxy_url: Option<&String>, + ) -> EmailResult<()> { + logger::info!("Email not sent as email support is disabled, please enable any of the supported email clients to send emails"); + Ok(()) + } +} diff --git a/crates/external_services/src/email/ses.rs b/crates/external_services/src/email/ses.rs index 73599b344cd1..f9dcc8f26adb 100644 --- a/crates/external_services/src/email/ses.rs +++ b/crates/external_services/src/email/ses.rs @@ -7,7 +7,7 @@ use aws_sdk_sesv2::{ Client, }; use aws_sdk_sts::config::Credentials; -use common_utils::{errors::CustomResult, ext_traits::OptionExt, pii}; +use common_utils::{errors::CustomResult, pii}; use error_stack::{report, ResultExt}; use hyper::Uri; use masking::PeekInterface; @@ -19,6 +19,7 @@ use crate::email::{EmailClient, EmailError, EmailResult, EmailSettings, Intermed #[derive(Debug, Clone)] pub struct AwsSes { sender: String, + ses_config: SESConfig, settings: EmailSettings, } @@ -32,6 +33,21 @@ pub struct SESConfig { pub sts_role_session_name: String, } +impl SESConfig { + /// Validation for the SES client specific configs + pub fn validate(&self) -> Result<(), &'static str> { + use common_utils::{ext_traits::ConfigExt, fp_utils::when}; + + when(self.email_role_arn.is_default_or_empty(), || { + Err("email.aws_ses.email_role_arn must not be empty") + })?; + + when(self.sts_role_session_name.is_default_or_empty(), || { + Err("email.aws_ses.sts_role_session_name must not be empty") + }) + } +} + /// Errors that could occur during SES operations. #[derive(Debug, thiserror::Error)] pub enum AwsSesError { @@ -67,15 +83,20 @@ pub enum AwsSesError { impl AwsSes { /// Constructs a new AwsSes client - pub async fn create(conf: &EmailSettings, proxy_url: Option>) -> Self { + pub async fn create( + conf: &EmailSettings, + ses_config: &SESConfig, + proxy_url: Option>, + ) -> Self { // Build the client initially which will help us know if the email configuration is correct - Self::create_client(conf, proxy_url) + Self::create_client(conf, ses_config, proxy_url) .await .map_err(|error| logger::error!(?error, "Failed to initialize SES Client")) .ok(); Self { sender: conf.sender_email.clone(), + ses_config: ses_config.clone(), settings: conf.clone(), } } @@ -83,19 +104,13 @@ impl AwsSes { /// A helper function to create ses client pub async fn create_client( conf: &EmailSettings, + ses_config: &SESConfig, proxy_url: Option>, ) -> CustomResult { let sts_config = Self::get_shared_config(conf.aws_region.to_owned(), proxy_url.as_ref())? .load() .await; - let ses_config = conf - .aws_ses - .as_ref() - .get_required_value("aws ses configuration") - .attach_printable("The selected email client is aws ses, but configuration is missing") - .change_context(AwsSesError::MissingConfigurationVariable("aws_ses"))?; - let role = aws_sdk_sts::Client::new(&sts_config) .assume_role() .role_arn(&ses_config.email_role_arn) @@ -219,7 +234,7 @@ impl EmailClient for AwsSes { ) -> EmailResult<()> { // Not using the same email client which was created at startup as the role session would expire // Create a client every time when the email is being sent - let email_client = Self::create_client(&self.settings, proxy_url) + let email_client = Self::create_client(&self.settings, &self.ses_config, proxy_url) .await .change_context(EmailError::ClientBuildingFailure)?; diff --git a/crates/external_services/src/email/smtp.rs b/crates/external_services/src/email/smtp.rs new file mode 100644 index 000000000000..33ab89f45712 --- /dev/null +++ b/crates/external_services/src/email/smtp.rs @@ -0,0 +1,189 @@ +use std::time::Duration; + +use common_utils::{errors::CustomResult, pii}; +use error_stack::ResultExt; +use lettre::{ + address::AddressError, + error, + message::{header::ContentType, Mailbox}, + transport::smtp::{self, authentication::Credentials}, + Message, SmtpTransport, Transport, +}; +use masking::{PeekInterface, Secret}; + +use crate::email::{EmailClient, EmailError, EmailResult, EmailSettings, IntermediateString}; + +/// Client for SMTP server operation +#[derive(Debug, Clone, Default, serde::Deserialize)] +pub struct SmtpServer { + /// sender email id + pub sender: String, + /// SMTP server specific configs + pub smtp_config: SmtpServerConfig, +} + +impl SmtpServer { + /// A helper function to create SMTP server client + pub fn create_client(&self) -> Result { + let host = self.smtp_config.host.clone(); + let port = self.smtp_config.port; + let timeout = Some(Duration::from_secs(self.smtp_config.timeout)); + let credentials = self + .smtp_config + .username + .clone() + .zip(self.smtp_config.password.clone()) + .map(|(username, password)| { + Credentials::new(username.peek().to_owned(), password.peek().to_owned()) + }); + match &self.smtp_config.connection { + SmtpConnection::StartTls => match credentials { + Some(credentials) => Ok(SmtpTransport::starttls_relay(&host) + .map_err(SmtpError::ConnectionFailure)? + .port(port) + .timeout(timeout) + .credentials(credentials) + .build()), + None => Ok(SmtpTransport::starttls_relay(&host) + .map_err(SmtpError::ConnectionFailure)? + .port(port) + .timeout(timeout) + .build()), + }, + SmtpConnection::Plaintext => match credentials { + Some(credentials) => Ok(SmtpTransport::builder_dangerous(&host) + .port(port) + .timeout(timeout) + .credentials(credentials) + .build()), + None => Ok(SmtpTransport::builder_dangerous(&host) + .port(port) + .timeout(timeout) + .build()), + }, + } + } + /// Constructs a new SMTP client + pub async fn create(conf: &EmailSettings, smtp_config: SmtpServerConfig) -> Self { + Self { + sender: conf.sender_email.clone(), + smtp_config: smtp_config.clone(), + } + } + /// helper function to convert email id into Mailbox + fn to_mail_box(email: String) -> EmailResult { + Ok(Mailbox::new( + None, + email + .parse() + .map_err(SmtpError::EmailParsingFailed) + .change_context(EmailError::EmailSendingFailure)?, + )) + } +} +/// Struct that contains the SMTP server specific configs required +#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "snake_case")] +pub struct SmtpServerConfig { + /// hostname of the SMTP server eg: smtp.gmail.com + pub host: String, + /// portname of the SMTP server eg: 25 + pub port: u16, + /// timeout for the SMTP server connection in seconds eg: 10 + pub timeout: u64, + /// Username name of the SMTP server + pub username: Option>, + /// Password of the SMTP server + pub password: Option>, + /// Connection type of the SMTP server + #[serde(default)] + pub connection: SmtpConnection, +} + +/// Enum that contains the connection types of the SMTP server +#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum SmtpConnection { + #[default] + /// Plaintext connection which MUST then successfully upgrade to TLS via STARTTLS + StartTls, + /// Plaintext connection (very insecure) + Plaintext, +} + +impl SmtpServerConfig { + /// Validation for the SMTP server client specific configs + pub fn validate(&self) -> Result<(), &'static str> { + use common_utils::{ext_traits::ConfigExt, fp_utils::when}; + when(self.host.is_default_or_empty(), || { + Err("email.smtp.host must not be empty") + })?; + self.username.clone().zip(self.password.clone()).map_or( + Ok(()), + |(username, password)| { + when(username.peek().is_default_or_empty(), || { + Err("email.smtp.username must not be empty") + })?; + when(password.peek().is_default_or_empty(), || { + Err("email.smtp.password must not be empty") + }) + }, + )?; + Ok(()) + } +} + +#[async_trait::async_trait] +impl EmailClient for SmtpServer { + type RichText = String; + fn convert_to_rich_text( + &self, + intermediate_string: IntermediateString, + ) -> CustomResult { + Ok(intermediate_string.into_inner()) + } + + async fn send_email( + &self, + recipient: pii::Email, + subject: String, + body: Self::RichText, + _proxy_url: Option<&String>, + ) -> EmailResult<()> { + // Create a client every time when the email is being sent + let email_client = + Self::create_client(self).change_context(EmailError::EmailSendingFailure)?; + + let email = Message::builder() + .to(Self::to_mail_box(recipient.peek().to_string())?) + .from(Self::to_mail_box(self.sender.clone())?) + .subject(subject) + .header(ContentType::TEXT_HTML) + .body(body) + .map_err(SmtpError::MessageBuildingFailed) + .change_context(EmailError::EmailSendingFailure)?; + + email_client + .send(&email) + .map_err(SmtpError::SendingFailure) + .change_context(EmailError::EmailSendingFailure)?; + Ok(()) + } +} + +/// Errors that could occur during SES operations. +#[derive(Debug, thiserror::Error)] +pub enum SmtpError { + /// An error occurred in the SMTP while sending email. + #[error("Failed to Send Email {0:?}")] + SendingFailure(smtp::Error), + /// An error occurred in the SMTP while building the message content. + #[error("Failed to create connection {0:?}")] + ConnectionFailure(smtp::Error), + /// An error occurred in the SMTP while building the message content. + #[error("Failed to Build Email content {0:?}")] + MessageBuildingFailed(error::Error), + /// An error occurred in the SMTP while building the message content. + #[error("Failed to parse given email {0:?}")] + EmailParsingFailed(AddressError), +} diff --git a/crates/external_services/src/file_storage.rs b/crates/external_services/src/file_storage.rs index e551cfee2af0..479b7c5f386c 100644 --- a/crates/external_services/src/file_storage.rs +++ b/crates/external_services/src/file_storage.rs @@ -1,6 +1,4 @@ -//! //! Module for managing file storage operations with support for multiple storage schemes. -//! use std::{ fmt::{Display, Formatter}, diff --git a/crates/external_services/src/file_storage/file_system.rs b/crates/external_services/src/file_storage/file_system.rs index e3986abf0f86..19b3185e81b4 100644 --- a/crates/external_services/src/file_storage/file_system.rs +++ b/crates/external_services/src/file_storage/file_system.rs @@ -1,6 +1,4 @@ -//! //! Module for local file system storage operations -//! use std::{ fs::{remove_file, File}, diff --git a/crates/external_services/src/grpc_client.rs b/crates/external_services/src/grpc_client.rs index 5afd30245519..404685025ed3 100644 --- a/crates/external_services/src/grpc_client.rs +++ b/crates/external_services/src/grpc_client.rs @@ -1,12 +1,32 @@ /// Dyanimc Routing Client interface implementation #[cfg(feature = "dynamic_routing")] pub mod dynamic_routing; +/// gRPC based Heath Check Client interface implementation +#[cfg(feature = "dynamic_routing")] +pub mod health_check_client; use std::{fmt::Debug, sync::Arc}; +#[cfg(feature = "dynamic_routing")] +use common_utils::consts; #[cfg(feature = "dynamic_routing")] use dynamic_routing::{DynamicRoutingClientConfig, RoutingStrategy}; +#[cfg(feature = "dynamic_routing")] +use health_check_client::HealthCheckClient; +#[cfg(feature = "dynamic_routing")] +use http_body_util::combinators::UnsyncBoxBody; +#[cfg(feature = "dynamic_routing")] +use hyper::body::Bytes; +#[cfg(feature = "dynamic_routing")] +use hyper_util::client::legacy::connect::HttpConnector; +#[cfg(feature = "dynamic_routing")] use router_env::logger; use serde; +#[cfg(feature = "dynamic_routing")] +use tonic::Status; + +#[cfg(feature = "dynamic_routing")] +/// Hyper based Client type for maintaining connection pool for all gRPC services +pub type Client = hyper_util::client::legacy::Client>; /// Struct contains all the gRPC Clients #[derive(Debug, Clone)] @@ -14,6 +34,9 @@ pub struct GrpcClients { /// The routing client #[cfg(feature = "dynamic_routing")] pub dynamic_routing: RoutingStrategy, + /// Health Check client for all gRPC services + #[cfg(feature = "dynamic_routing")] + pub health_client: HealthCheckClient, } /// Type that contains the configs required to construct a gRPC client with its respective services. #[derive(Debug, Clone, serde::Deserialize, serde::Serialize, Default)] @@ -30,19 +53,78 @@ impl GrpcClientSettings { /// This function will be called at service startup. #[allow(clippy::expect_used)] pub async fn get_grpc_client_interface(&self) -> Arc { + #[cfg(feature = "dynamic_routing")] + let client = + hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()) + .http2_only(true) + .build_http(); + #[cfg(feature = "dynamic_routing")] let dynamic_routing_connection = self .dynamic_routing_client .clone() - .get_dynamic_routing_connection() + .get_dynamic_routing_connection(client.clone()) .await .expect("Failed to establish a connection with the Dynamic Routing Server"); - logger::info!("Connection established with gRPC Server"); + #[cfg(feature = "dynamic_routing")] + let health_client = HealthCheckClient::build_connections(self, client) + .await + .expect("Failed to build gRPC connections"); Arc::new(GrpcClients { #[cfg(feature = "dynamic_routing")] dynamic_routing: dynamic_routing_connection, + #[cfg(feature = "dynamic_routing")] + health_client, }) } } + +/// Contains grpc headers +#[derive(Debug)] +pub struct GrpcHeaders { + /// Tenant id + pub tenant_id: String, + /// Request id + pub request_id: Option, +} + +#[cfg(feature = "dynamic_routing")] +/// Trait to add necessary headers to the tonic Request +pub(crate) trait AddHeaders { + /// Add necessary header fields to the tonic Request + fn add_headers_to_grpc_request(&mut self, headers: GrpcHeaders); +} + +#[cfg(feature = "dynamic_routing")] +impl AddHeaders for tonic::Request { + #[track_caller] + fn add_headers_to_grpc_request(&mut self, headers: GrpcHeaders) { + headers.tenant_id + .parse() + .map(|tenant_id| { + self + .metadata_mut() + .append(consts::TENANT_HEADER, tenant_id) + }) + .inspect_err( + |err| logger::warn!(header_parse_error=?err,"invalid {} received",consts::TENANT_HEADER), + ) + .ok(); + + headers.request_id.map(|request_id| { + request_id + .parse() + .map(|request_id| { + self + .metadata_mut() + .append(consts::X_REQUEST_ID, request_id) + }) + .inspect_err( + |err| logger::warn!(header_parse_error=?err,"invalid {} received",consts::X_REQUEST_ID), + ) + .ok(); + }); + } +} diff --git a/crates/external_services/src/grpc_client/dynamic_routing.rs b/crates/external_services/src/grpc_client/dynamic_routing.rs index 2681bffb0ce3..e5050227fa89 100644 --- a/crates/external_services/src/grpc_client/dynamic_routing.rs +++ b/crates/external_services/src/grpc_client/dynamic_routing.rs @@ -1,28 +1,17 @@ use std::fmt::Debug; -use api_models::routing::{ - CurrentBlockThreshold, RoutableConnectorChoice, RoutableConnectorChoiceWithStatus, - SuccessBasedRoutingConfig, SuccessBasedRoutingConfigBody, -}; -use common_utils::{errors::CustomResult, ext_traits::OptionExt, transformers::ForeignTryFrom}; -use error_stack::ResultExt; +use common_utils::errors::CustomResult; +use router_env::logger; use serde; -use success_rate::{ - success_rate_calculator_client::SuccessRateCalculatorClient, CalSuccessRateConfig, - CalSuccessRateRequest, CalSuccessRateResponse, - CurrentBlockThreshold as DynamicCurrentThreshold, LabelWithStatus, - UpdateSuccessRateWindowConfig, UpdateSuccessRateWindowRequest, UpdateSuccessRateWindowResponse, -}; -use tonic::transport::Channel; -#[allow( - missing_docs, - unused_qualifications, - clippy::unwrap_used, - clippy::as_conversions -)] -pub mod success_rate { - tonic::include_proto!("success_rate"); -} +/// Elimination Routing Client Interface Implementation +pub mod elimination_rate_client; +/// Success Routing Client Interface Implementation +pub mod success_rate_client; + +pub use elimination_rate_client::EliminationAnalyserClient; +pub use success_rate_client::SuccessRateCalculatorClient; + +use super::Client; /// Result type for Dynamic Routing pub type DynamicRoutingResult = CustomResult; @@ -35,16 +24,21 @@ pub enum DynamicRoutingError { /// The required field name field: String, }, - /// Error from Dynamic Routing Server - #[error("Error from Dynamic Routing Server : {0}")] + /// Error from Dynamic Routing Server while performing success_rate analysis + #[error("Error from Dynamic Routing Server while perfrming success_rate analysis : {0}")] SuccessRateBasedRoutingFailure(String), + /// Error from Dynamic Routing Server while perfrming elimination + #[error("Error from Dynamic Routing Server while perfrming elimination : {0}")] + EliminationRateRoutingFailure(String), } /// Type that consists of all the services provided by the client #[derive(Debug, Clone)] pub struct RoutingStrategy { /// success rate service for Dynamic Routing - pub success_rate_client: Option>, + pub success_rate_client: Option>, + /// elimination service for Dynamic Routing + pub elimination_rate_client: Option>, } /// Contains the Dynamic Routing Client Config @@ -57,6 +51,8 @@ pub enum DynamicRoutingClientConfig { host: String, /// The port of the client port: u16, + /// Service name + service: String, }, #[default] /// If the dynamic routing client config has been disabled @@ -67,199 +63,25 @@ impl DynamicRoutingClientConfig { /// establish connection with the server pub async fn get_dynamic_routing_connection( self, + client: Client, ) -> Result> { - let success_rate_client = match self { - Self::Enabled { host, port } => { - let uri = format!("http://{}:{}", host, port); - let channel = tonic::transport::Endpoint::new(uri)?.connect().await?; - Some(SuccessRateCalculatorClient::new(channel)) + let (success_rate_client, elimination_rate_client) = match self { + Self::Enabled { host, port, .. } => { + let uri = format!("http://{}:{}", host, port).parse::()?; + logger::info!("Connection established with dynamic routing gRPC Server"); + ( + Some(SuccessRateCalculatorClient::with_origin( + client.clone(), + uri.clone(), + )), + Some(EliminationAnalyserClient::with_origin(client, uri)), + ) } - Self::Disabled => None, + Self::Disabled => (None, None), }; Ok(RoutingStrategy { success_rate_client, - }) - } -} - -/// The trait Success Based Dynamic Routing would have the functions required to support the calculation and updation window -#[async_trait::async_trait] -pub trait SuccessBasedDynamicRouting: dyn_clone::DynClone + Send + Sync { - /// To calculate the success rate for the list of chosen connectors - async fn calculate_success_rate( - &self, - id: String, - success_rate_based_config: SuccessBasedRoutingConfig, - label_input: Vec, - ) -> DynamicRoutingResult; - /// To update the success rate with the given label - async fn update_success_rate( - &self, - id: String, - success_rate_based_config: SuccessBasedRoutingConfig, - response: Vec, - ) -> DynamicRoutingResult; -} - -#[async_trait::async_trait] -impl SuccessBasedDynamicRouting for SuccessRateCalculatorClient { - async fn calculate_success_rate( - &self, - id: String, - success_rate_based_config: SuccessBasedRoutingConfig, - label_input: Vec, - ) -> DynamicRoutingResult { - let params = success_rate_based_config - .params - .map(|vec| { - vec.into_iter().fold(String::new(), |mut acc_str, params| { - if !acc_str.is_empty() { - acc_str.push(':') - } - acc_str.push_str(params.to_string().as_str()); - acc_str - }) - }) - .get_required_value("params") - .change_context(DynamicRoutingError::MissingRequiredField { - field: "params".to_string(), - })?; - - let labels = label_input - .into_iter() - .map(|conn_choice| conn_choice.to_string()) - .collect::>(); - - let config = success_rate_based_config - .config - .map(ForeignTryFrom::foreign_try_from) - .transpose()?; - - let request = tonic::Request::new(CalSuccessRateRequest { - id, - params, - labels, - config, - }); - - let mut client = self.clone(); - - let response = client - .fetch_success_rate(request) - .await - .change_context(DynamicRoutingError::SuccessRateBasedRoutingFailure( - "Failed to fetch the success rate".to_string(), - ))? - .into_inner(); - - Ok(response) - } - - async fn update_success_rate( - &self, - id: String, - success_rate_based_config: SuccessBasedRoutingConfig, - label_input: Vec, - ) -> DynamicRoutingResult { - let config = success_rate_based_config - .config - .map(ForeignTryFrom::foreign_try_from) - .transpose()?; - - let labels_with_status = label_input - .into_iter() - .map(|conn_choice| LabelWithStatus { - label: conn_choice.routable_connector_choice.to_string(), - status: conn_choice.status, - }) - .collect(); - - let params = success_rate_based_config - .params - .map(|vec| { - vec.into_iter().fold(String::new(), |mut acc_str, params| { - if !acc_str.is_empty() { - acc_str.push(':') - } - acc_str.push_str(params.to_string().as_str()); - acc_str - }) - }) - .get_required_value("params") - .change_context(DynamicRoutingError::MissingRequiredField { - field: "params".to_string(), - })?; - - let request = tonic::Request::new(UpdateSuccessRateWindowRequest { - id, - params, - labels_with_status, - config, - }); - - let mut client = self.clone(); - - let response = client - .update_success_rate_window(request) - .await - .change_context(DynamicRoutingError::SuccessRateBasedRoutingFailure( - "Failed to update the success rate window".to_string(), - ))? - .into_inner(); - - Ok(response) - } -} - -impl ForeignTryFrom for DynamicCurrentThreshold { - type Error = error_stack::Report; - fn foreign_try_from(current_threshold: CurrentBlockThreshold) -> Result { - Ok(Self { - duration_in_mins: current_threshold.duration_in_mins, - max_total_count: current_threshold - .max_total_count - .get_required_value("max_total_count") - .change_context(DynamicRoutingError::MissingRequiredField { - field: "max_total_count".to_string(), - })?, - }) - } -} - -impl ForeignTryFrom for UpdateSuccessRateWindowConfig { - type Error = error_stack::Report; - fn foreign_try_from(config: SuccessBasedRoutingConfigBody) -> Result { - Ok(Self { - max_aggregates_size: config - .max_aggregates_size - .get_required_value("max_aggregate_size") - .change_context(DynamicRoutingError::MissingRequiredField { - field: "max_aggregates_size".to_string(), - })?, - current_block_threshold: config - .current_block_threshold - .map(ForeignTryFrom::foreign_try_from) - .transpose()?, - }) - } -} - -impl ForeignTryFrom for CalSuccessRateConfig { - type Error = error_stack::Report; - fn foreign_try_from(config: SuccessBasedRoutingConfigBody) -> Result { - Ok(Self { - min_aggregates_size: config - .min_aggregates_size - .get_required_value("min_aggregate_size") - .change_context(DynamicRoutingError::MissingRequiredField { - field: "min_aggregates_size".to_string(), - })?, - default_success_rate: config - .default_success_rate - .get_required_value("default_success_rate") - .change_context(DynamicRoutingError::MissingRequiredField { - field: "default_success_rate".to_string(), - })?, + elimination_rate_client, }) } } diff --git a/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs b/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs new file mode 100644 index 000000000000..bc5ce4997270 --- /dev/null +++ b/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs @@ -0,0 +1,171 @@ +use api_models::routing::{ + EliminationAnalyserConfig as EliminationConfig, RoutableConnectorChoice, + RoutableConnectorChoiceWithBucketName, +}; +use common_utils::{ext_traits::OptionExt, transformers::ForeignTryFrom}; +pub use elimination_rate::{ + elimination_analyser_client::EliminationAnalyserClient, EliminationBucketConfig, + EliminationRequest, EliminationResponse, InvalidateBucketRequest, InvalidateBucketResponse, + LabelWithBucketName, UpdateEliminationBucketRequest, UpdateEliminationBucketResponse, +}; +use error_stack::ResultExt; +#[allow( + missing_docs, + unused_qualifications, + clippy::unwrap_used, + clippy::as_conversions, + clippy::use_self +)] +pub mod elimination_rate { + tonic::include_proto!("elimination"); +} + +use super::{Client, DynamicRoutingError, DynamicRoutingResult}; +use crate::grpc_client::{AddHeaders, GrpcHeaders}; + +/// The trait Elimination Based Routing would have the functions required to support performance, calculation and invalidation bucket +#[async_trait::async_trait] +pub trait EliminationBasedRouting: dyn_clone::DynClone + Send + Sync { + /// To perform the elimination based routing for the list of connectors + async fn perform_elimination_routing( + &self, + id: String, + params: String, + labels: Vec, + configs: Option, + headers: GrpcHeaders, + ) -> DynamicRoutingResult; + /// To update the bucket size and ttl for list of connectors with its respective bucket name + async fn update_elimination_bucket_config( + &self, + id: String, + params: String, + report: Vec, + config: Option, + headers: GrpcHeaders, + ) -> DynamicRoutingResult; + /// To invalidate the previous id's bucket + async fn invalidate_elimination_bucket( + &self, + id: String, + headers: GrpcHeaders, + ) -> DynamicRoutingResult; +} + +#[async_trait::async_trait] +impl EliminationBasedRouting for EliminationAnalyserClient { + async fn perform_elimination_routing( + &self, + id: String, + params: String, + label_input: Vec, + configs: Option, + headers: GrpcHeaders, + ) -> DynamicRoutingResult { + let labels = label_input + .into_iter() + .map(|conn_choice| conn_choice.to_string()) + .collect::>(); + + let config = configs.map(ForeignTryFrom::foreign_try_from).transpose()?; + + let mut request = tonic::Request::new(EliminationRequest { + id, + params, + labels, + config, + }); + + request.add_headers_to_grpc_request(headers); + + let response = self + .clone() + .get_elimination_status(request) + .await + .change_context(DynamicRoutingError::EliminationRateRoutingFailure( + "Failed to perform the elimination analysis".to_string(), + ))? + .into_inner(); + + Ok(response) + } + + async fn update_elimination_bucket_config( + &self, + id: String, + params: String, + report: Vec, + configs: Option, + headers: GrpcHeaders, + ) -> DynamicRoutingResult { + let config = configs.map(ForeignTryFrom::foreign_try_from).transpose()?; + + let labels_with_bucket_name = report + .into_iter() + .map(|conn_choice_with_bucket| LabelWithBucketName { + label: conn_choice_with_bucket + .routable_connector_choice + .to_string(), + bucket_name: conn_choice_with_bucket.bucket_name, + }) + .collect::>(); + + let mut request = tonic::Request::new(UpdateEliminationBucketRequest { + id, + params, + labels_with_bucket_name, + config, + }); + + request.add_headers_to_grpc_request(headers); + + let response = self + .clone() + .update_elimination_bucket(request) + .await + .change_context(DynamicRoutingError::EliminationRateRoutingFailure( + "Failed to update the elimination bucket".to_string(), + ))? + .into_inner(); + Ok(response) + } + async fn invalidate_elimination_bucket( + &self, + id: String, + headers: GrpcHeaders, + ) -> DynamicRoutingResult { + let mut request = tonic::Request::new(InvalidateBucketRequest { id }); + + request.add_headers_to_grpc_request(headers); + + let response = self + .clone() + .invalidate_bucket(request) + .await + .change_context(DynamicRoutingError::EliminationRateRoutingFailure( + "Failed to invalidate the elimination bucket".to_string(), + ))? + .into_inner(); + Ok(response) + } +} + +impl ForeignTryFrom for EliminationBucketConfig { + type Error = error_stack::Report; + fn foreign_try_from(config: EliminationConfig) -> Result { + Ok(Self { + bucket_size: config + .bucket_size + .get_required_value("bucket_size") + .change_context(DynamicRoutingError::MissingRequiredField { + field: "bucket_size".to_string(), + })?, + bucket_leak_interval_in_secs: config + .bucket_leak_interval_in_secs + .get_required_value("bucket_leak_interval_in_secs") + .change_context(DynamicRoutingError::MissingRequiredField { + field: "bucket_leak_interval_in_secs".to_string(), + })?, + }) + } +} diff --git a/crates/external_services/src/grpc_client/dynamic_routing/success_rate_client.rs b/crates/external_services/src/grpc_client/dynamic_routing/success_rate_client.rs new file mode 100644 index 000000000000..3cf06ab63beb --- /dev/null +++ b/crates/external_services/src/grpc_client/dynamic_routing/success_rate_client.rs @@ -0,0 +1,208 @@ +use api_models::routing::{ + CurrentBlockThreshold, RoutableConnectorChoice, RoutableConnectorChoiceWithStatus, + SuccessBasedRoutingConfig, SuccessBasedRoutingConfigBody, +}; +use common_utils::{ext_traits::OptionExt, transformers::ForeignTryFrom}; +use error_stack::ResultExt; +pub use success_rate::{ + success_rate_calculator_client::SuccessRateCalculatorClient, CalSuccessRateConfig, + CalSuccessRateRequest, CalSuccessRateResponse, + CurrentBlockThreshold as DynamicCurrentThreshold, InvalidateWindowsRequest, + InvalidateWindowsResponse, LabelWithStatus, UpdateSuccessRateWindowConfig, + UpdateSuccessRateWindowRequest, UpdateSuccessRateWindowResponse, +}; +#[allow( + missing_docs, + unused_qualifications, + clippy::unwrap_used, + clippy::as_conversions +)] +pub mod success_rate { + tonic::include_proto!("success_rate"); +} +use super::{Client, DynamicRoutingError, DynamicRoutingResult}; +use crate::grpc_client::{AddHeaders, GrpcHeaders}; +/// The trait Success Based Dynamic Routing would have the functions required to support the calculation and updation window +#[async_trait::async_trait] +pub trait SuccessBasedDynamicRouting: dyn_clone::DynClone + Send + Sync { + /// To calculate the success rate for the list of chosen connectors + async fn calculate_success_rate( + &self, + id: String, + success_rate_based_config: SuccessBasedRoutingConfig, + params: String, + label_input: Vec, + headers: GrpcHeaders, + ) -> DynamicRoutingResult; + /// To update the success rate with the given label + async fn update_success_rate( + &self, + id: String, + success_rate_based_config: SuccessBasedRoutingConfig, + params: String, + response: Vec, + headers: GrpcHeaders, + ) -> DynamicRoutingResult; + /// To invalidates the success rate routing keys + async fn invalidate_success_rate_routing_keys( + &self, + id: String, + headers: GrpcHeaders, + ) -> DynamicRoutingResult; +} + +#[async_trait::async_trait] +impl SuccessBasedDynamicRouting for SuccessRateCalculatorClient { + async fn calculate_success_rate( + &self, + id: String, + success_rate_based_config: SuccessBasedRoutingConfig, + params: String, + label_input: Vec, + headers: GrpcHeaders, + ) -> DynamicRoutingResult { + let labels = label_input + .into_iter() + .map(|conn_choice| conn_choice.to_string()) + .collect::>(); + + let config = success_rate_based_config + .config + .map(ForeignTryFrom::foreign_try_from) + .transpose()?; + + let mut request = tonic::Request::new(CalSuccessRateRequest { + id, + params, + labels, + config, + }); + + request.add_headers_to_grpc_request(headers); + + let response = self + .clone() + .fetch_success_rate(request) + .await + .change_context(DynamicRoutingError::SuccessRateBasedRoutingFailure( + "Failed to fetch the success rate".to_string(), + ))? + .into_inner(); + + Ok(response) + } + + async fn update_success_rate( + &self, + id: String, + success_rate_based_config: SuccessBasedRoutingConfig, + params: String, + label_input: Vec, + headers: GrpcHeaders, + ) -> DynamicRoutingResult { + let config = success_rate_based_config + .config + .map(ForeignTryFrom::foreign_try_from) + .transpose()?; + + let labels_with_status = label_input + .into_iter() + .map(|conn_choice| LabelWithStatus { + label: conn_choice.routable_connector_choice.to_string(), + status: conn_choice.status, + }) + .collect(); + + let mut request = tonic::Request::new(UpdateSuccessRateWindowRequest { + id, + params, + labels_with_status, + config, + }); + + request.add_headers_to_grpc_request(headers); + + let response = self + .clone() + .update_success_rate_window(request) + .await + .change_context(DynamicRoutingError::SuccessRateBasedRoutingFailure( + "Failed to update the success rate window".to_string(), + ))? + .into_inner(); + + Ok(response) + } + async fn invalidate_success_rate_routing_keys( + &self, + id: String, + headers: GrpcHeaders, + ) -> DynamicRoutingResult { + let mut request = tonic::Request::new(InvalidateWindowsRequest { id }); + + request.add_headers_to_grpc_request(headers); + + let response = self + .clone() + .invalidate_windows(request) + .await + .change_context(DynamicRoutingError::SuccessRateBasedRoutingFailure( + "Failed to invalidate the success rate routing keys".to_string(), + ))? + .into_inner(); + Ok(response) + } +} + +impl ForeignTryFrom for DynamicCurrentThreshold { + type Error = error_stack::Report; + fn foreign_try_from(current_threshold: CurrentBlockThreshold) -> Result { + Ok(Self { + duration_in_mins: current_threshold.duration_in_mins, + max_total_count: current_threshold + .max_total_count + .get_required_value("max_total_count") + .change_context(DynamicRoutingError::MissingRequiredField { + field: "max_total_count".to_string(), + })?, + }) + } +} + +impl ForeignTryFrom for UpdateSuccessRateWindowConfig { + type Error = error_stack::Report; + fn foreign_try_from(config: SuccessBasedRoutingConfigBody) -> Result { + Ok(Self { + max_aggregates_size: config + .max_aggregates_size + .get_required_value("max_aggregate_size") + .change_context(DynamicRoutingError::MissingRequiredField { + field: "max_aggregates_size".to_string(), + })?, + current_block_threshold: config + .current_block_threshold + .map(ForeignTryFrom::foreign_try_from) + .transpose()?, + }) + } +} + +impl ForeignTryFrom for CalSuccessRateConfig { + type Error = error_stack::Report; + fn foreign_try_from(config: SuccessBasedRoutingConfigBody) -> Result { + Ok(Self { + min_aggregates_size: config + .min_aggregates_size + .get_required_value("min_aggregate_size") + .change_context(DynamicRoutingError::MissingRequiredField { + field: "min_aggregates_size".to_string(), + })?, + default_success_rate: config + .default_success_rate + .get_required_value("default_success_rate") + .change_context(DynamicRoutingError::MissingRequiredField { + field: "default_success_rate".to_string(), + })?, + }) + } +} diff --git a/crates/external_services/src/grpc_client/health_check_client.rs b/crates/external_services/src/grpc_client/health_check_client.rs new file mode 100644 index 000000000000..94d78df7955c --- /dev/null +++ b/crates/external_services/src/grpc_client/health_check_client.rs @@ -0,0 +1,149 @@ +use std::{collections::HashMap, fmt::Debug}; + +use api_models::health_check::{HealthCheckMap, HealthCheckServices}; +use common_utils::{errors::CustomResult, ext_traits::AsyncExt}; +use error_stack::ResultExt; +pub use health_check::{ + health_check_response::ServingStatus, health_client::HealthClient, HealthCheckRequest, + HealthCheckResponse, +}; +use router_env::logger; + +#[allow( + missing_docs, + unused_qualifications, + clippy::unwrap_used, + clippy::as_conversions, + clippy::use_self +)] +pub mod health_check { + tonic::include_proto!("grpc.health.v1"); +} + +use super::{Client, DynamicRoutingClientConfig, GrpcClientSettings}; + +/// Result type for Dynamic Routing +pub type HealthCheckResult = CustomResult; +/// Dynamic Routing Errors +#[derive(Debug, Clone, thiserror::Error)] +pub enum HealthCheckError { + /// The required input is missing + #[error("Missing fields: {0} for building the Health check connection")] + MissingFields(String), + /// Error from gRPC Server + #[error("Error from gRPC Server : {0}")] + ConnectionError(String), + /// status is invalid + #[error("Invalid Status from server")] + InvalidStatus, +} + +/// Health Check Client type +#[derive(Debug, Clone)] +pub struct HealthCheckClient { + /// Health clients for all gRPC based services + pub clients: HashMap>, +} + +impl HealthCheckClient { + /// Build connections to all gRPC services + pub async fn build_connections( + config: &GrpcClientSettings, + client: Client, + ) -> Result> { + let dynamic_routing_config = &config.dynamic_routing_client; + let connection = match dynamic_routing_config { + DynamicRoutingClientConfig::Enabled { + host, + port, + service, + } => Some((host.clone(), *port, service.clone())), + _ => None, + }; + + let mut client_map = HashMap::new(); + + if let Some(conn) = connection { + let uri = format!("http://{}:{}", conn.0, conn.1).parse::()?; + let health_client = HealthClient::with_origin(client, uri); + + client_map.insert(HealthCheckServices::DynamicRoutingService, health_client); + } + + Ok(Self { + clients: client_map, + }) + } + /// Perform health check for all services involved + pub async fn perform_health_check( + &self, + config: &GrpcClientSettings, + ) -> HealthCheckResult { + let dynamic_routing_config = &config.dynamic_routing_client; + let connection = match dynamic_routing_config { + DynamicRoutingClientConfig::Enabled { + host, + port, + service, + } => Some((host.clone(), *port, service.clone())), + _ => None, + }; + + let health_client = self + .clients + .get(&HealthCheckServices::DynamicRoutingService); + + // SAFETY : This is a safe cast as there exists a valid + // integer value for this variant + #[allow(clippy::as_conversions)] + let expected_status = ServingStatus::Serving as i32; + + let mut service_map = HealthCheckMap::new(); + + let health_check_succeed = connection + .as_ref() + .async_map(|conn| self.get_response_from_grpc_service(conn.2.clone(), health_client)) + .await + .transpose() + .change_context(HealthCheckError::ConnectionError( + "error calling dynamic routing service".to_string(), + )) + .map_err(|err| logger::error!(error=?err)) + .ok() + .flatten() + .is_some_and(|resp| resp.status == expected_status); + + connection.and_then(|_conn| { + service_map.insert( + HealthCheckServices::DynamicRoutingService, + health_check_succeed, + ) + }); + + Ok(service_map) + } + + async fn get_response_from_grpc_service( + &self, + service: String, + client: Option<&HealthClient>, + ) -> HealthCheckResult { + let request = tonic::Request::new(HealthCheckRequest { service }); + + let mut client = client + .ok_or(HealthCheckError::MissingFields( + "[health_client]".to_string(), + ))? + .clone(); + + let response = client + .check(request) + .await + .change_context(HealthCheckError::ConnectionError( + "Failed to call dynamic routing service".to_string(), + ))? + .into_inner(); + + Ok(response) + } +} diff --git a/crates/external_services/src/hashicorp_vault/core.rs b/crates/external_services/src/hashicorp_vault/core.rs index 15edcb6418cd..3cc03b4330b1 100644 --- a/crates/external_services/src/hashicorp_vault/core.rs +++ b/crates/external_services/src/hashicorp_vault/core.rs @@ -103,7 +103,6 @@ impl HashiCorpVault { /// # Parameters /// /// - `config`: A reference to a `HashiCorpVaultConfig` containing the configuration details. - /// pub fn new(config: &HashiCorpVaultConfig) -> error_stack::Result { VaultClient::new( VaultClientSettingsBuilder::default() @@ -129,7 +128,6 @@ impl HashiCorpVault { /// /// - `En`: The engine type that implements the `Engine` trait. /// - `I`: The type that can be constructed from the retrieved encoded data. - /// pub async fn fetch(&self, data: String) -> error_stack::Result where for<'a> En: Engine< diff --git a/crates/external_services/src/lib.rs b/crates/external_services/src/lib.rs index 4570a5e59605..304ce248317a 100644 --- a/crates/external_services/src/lib.rs +++ b/crates/external_services/src/lib.rs @@ -30,9 +30,8 @@ pub mod consts { /// Metrics for interactions with external systems. #[cfg(feature = "aws_kms")] pub mod metrics { - use router_env::{counter_metric, global_meter, histogram_metric, metrics_context}; + use router_env::{counter_metric, global_meter, histogram_metric_f64}; - metrics_context!(CONTEXT); global_meter!(GLOBAL_METER, "EXTERNAL_SERVICES"); #[cfg(feature = "aws_kms")] @@ -41,7 +40,7 @@ pub mod metrics { counter_metric!(AWS_KMS_ENCRYPTION_FAILURES, GLOBAL_METER); // No. of AWS KMS Encryption failures #[cfg(feature = "aws_kms")] - histogram_metric!(AWS_KMS_DECRYPT_TIME, GLOBAL_METER); // Histogram for AWS KMS decryption time (in sec) + histogram_metric_f64!(AWS_KMS_DECRYPT_TIME, GLOBAL_METER); // Histogram for AWS KMS decryption time (in sec) #[cfg(feature = "aws_kms")] - histogram_metric!(AWS_KMS_ENCRYPT_TIME, GLOBAL_METER); // Histogram for AWS KMS encryption time (in sec) + histogram_metric_f64!(AWS_KMS_ENCRYPT_TIME, GLOBAL_METER); // Histogram for AWS KMS encryption time (in sec) } diff --git a/crates/external_services/src/managers/encryption_management.rs b/crates/external_services/src/managers/encryption_management.rs index 678239c60b1a..a0add638235c 100644 --- a/crates/external_services/src/managers/encryption_management.rs +++ b/crates/external_services/src/managers/encryption_management.rs @@ -1,6 +1,4 @@ -//! //! Encryption management util module -//! use std::sync::Arc; diff --git a/crates/external_services/src/managers/secrets_management.rs b/crates/external_services/src/managers/secrets_management.rs index b79046b4c75f..7b27e74bf34b 100644 --- a/crates/external_services/src/managers/secrets_management.rs +++ b/crates/external_services/src/managers/secrets_management.rs @@ -1,6 +1,4 @@ -//! //! Secrets management util module -//! use common_utils::errors::CustomResult; #[cfg(feature = "hashicorp-vault")] diff --git a/crates/external_services/src/no_encryption.rs b/crates/external_services/src/no_encryption.rs index 17c29618f89a..6f805fc7a10f 100644 --- a/crates/external_services/src/no_encryption.rs +++ b/crates/external_services/src/no_encryption.rs @@ -1,6 +1,4 @@ -//! //! No encryption functionalities -//! pub mod core; diff --git a/crates/hyperswitch_connectors/Cargo.toml b/crates/hyperswitch_connectors/Cargo.toml index 2676f6372230..ca9e77283cda 100644 --- a/crates/hyperswitch_connectors/Cargo.toml +++ b/crates/hyperswitch_connectors/Cargo.toml @@ -8,7 +8,7 @@ license.workspace = true [features] frm = ["hyperswitch_domain_models/frm", "hyperswitch_interfaces/frm"] payouts = ["hyperswitch_domain_models/payouts", "api_models/payouts", "hyperswitch_interfaces/payouts"] -v1 = ["api_models/v1", "hyperswitch_domain_models/v1"] +v1 = ["api_models/v1", "hyperswitch_domain_models/v1", "common_utils/v1"] [dependencies] actix-http = "3.6.0" @@ -16,23 +16,31 @@ actix-web = "4.5.1" async-trait = "0.1.79" base64 = "0.22.0" bytes = "1.6.0" +encoding_rs = "0.8.33" error-stack = "0.4.1" hex = "0.4.3" http = "0.2.12" image = { version = "0.25.1", default-features = false, features = ["png"] } +mime = "0.3.17" once_cell = "1.19.0" qrcode = "0.14.0" +quick-xml = { version = "0.31.0", features = ["serialize"] } rand = "0.8.5" regex = "1.10.4" reqwest = { version = "0.11.27" } ring = "0.17.8" +roxmltree = "0.19.0" serde = { version = "1.0.197", features = ["derive"] } serde_json = "1.0.115" +serde_qs = "0.12.0" serde_urlencoded = "0.7.1" +serde_with = "3.7.0" strum = { version = "0.26", features = ["derive"] } time = "0.3.35" url = "2.5.0" +urlencoding = "2.1.3" uuid = { version = "1.8.0", features = ["v4"] } +lazy_static = "1.4.0" # First party crates api_models = { version = "0.1.0", path = "../api_models", features = ["errors"], default-features = false } diff --git a/crates/hyperswitch_connectors/src/connectors.rs b/crates/hyperswitch_connectors/src/connectors.rs index 9406353a1ee7..d5a695fe01d7 100644 --- a/crates/hyperswitch_connectors/src/connectors.rs +++ b/crates/hyperswitch_connectors/src/connectors.rs @@ -1,31 +1,69 @@ +pub mod airwallex; +pub mod amazonpay; pub mod bambora; +pub mod bamboraapac; +pub mod billwerk; pub mod bitpay; +pub mod bluesnap; +pub mod boku; pub mod cashtocode; pub mod coinbase; pub mod cryptopay; +pub mod ctp_mastercard; +pub mod datatrans; pub mod deutschebank; +pub mod digitalvirgo; pub mod dlocal; +pub mod elavon; pub mod fiserv; pub mod fiservemea; pub mod fiuu; +pub mod forte; pub mod globepay; +pub mod gocardless; pub mod helcim; +pub mod inespay; +pub mod jpmorgan; pub mod mollie; +pub mod multisafepay; +pub mod nexinets; pub mod nexixpay; +pub mod nomupay; pub mod novalnet; +pub mod paybox; +pub mod payeezy; +pub mod payu; +pub mod placetopay; pub mod powertranz; +pub mod prophetpay; +pub mod rapyd; +pub mod razorpay; +pub mod redsys; +pub mod shift4; pub mod square; pub mod stax; pub mod taxjar; pub mod thunes; pub mod tsys; +pub mod unified_authentication_service; pub mod volt; pub mod worldline; +pub mod worldpay; +pub mod xendit; +pub mod zen; +pub mod zsl; pub use self::{ - bambora::Bambora, bitpay::Bitpay, cashtocode::Cashtocode, coinbase::Coinbase, - cryptopay::Cryptopay, deutschebank::Deutschebank, dlocal::Dlocal, fiserv::Fiserv, - fiservemea::Fiservemea, fiuu::Fiuu, globepay::Globepay, helcim::Helcim, mollie::Mollie, - nexixpay::Nexixpay, novalnet::Novalnet, powertranz::Powertranz, square::Square, stax::Stax, - taxjar::Taxjar, thunes::Thunes, tsys::Tsys, volt::Volt, worldline::Worldline, + airwallex::Airwallex, amazonpay::Amazonpay, bambora::Bambora, bamboraapac::Bamboraapac, + billwerk::Billwerk, bitpay::Bitpay, bluesnap::Bluesnap, boku::Boku, cashtocode::Cashtocode, + coinbase::Coinbase, cryptopay::Cryptopay, ctp_mastercard::CtpMastercard, datatrans::Datatrans, + deutschebank::Deutschebank, digitalvirgo::Digitalvirgo, dlocal::Dlocal, elavon::Elavon, + fiserv::Fiserv, fiservemea::Fiservemea, fiuu::Fiuu, forte::Forte, globepay::Globepay, + gocardless::Gocardless, helcim::Helcim, inespay::Inespay, jpmorgan::Jpmorgan, mollie::Mollie, + multisafepay::Multisafepay, nexinets::Nexinets, nexixpay::Nexixpay, nomupay::Nomupay, + novalnet::Novalnet, paybox::Paybox, payeezy::Payeezy, payu::Payu, placetopay::Placetopay, + powertranz::Powertranz, prophetpay::Prophetpay, rapyd::Rapyd, razorpay::Razorpay, + redsys::Redsys, shift4::Shift4, square::Square, stax::Stax, taxjar::Taxjar, thunes::Thunes, + tsys::Tsys, unified_authentication_service::UnifiedAuthenticationService, volt::Volt, + worldline::Worldline, worldpay::Worldpay, xendit::Xendit, zen::Zen, zsl::Zsl, }; diff --git a/crates/router/src/connector/airwallex.rs b/crates/hyperswitch_connectors/src/connectors/airwallex.rs similarity index 72% rename from crates/router/src/connector/airwallex.rs rename to crates/hyperswitch_connectors/src/connectors/airwallex.rs index e6b0113e98cb..f37bef830269 100644 --- a/crates/router/src/connector/airwallex.rs +++ b/crates/hyperswitch_connectors/src/connectors/airwallex.rs @@ -1,35 +1,56 @@ pub mod transformers; + use std::fmt::Debug; +use api_models::webhooks::IncomingWebhookEvent; +use common_enums::{enums, CallConnectorAction, PaymentAction}; use common_utils::{ - ext_traits::{ByteSliceExt, ValueExt}, - request::RequestContent, + crypto, + errors::CustomResult, + ext_traits::{ByteSliceExt, BytesExt, ValueExt}, + request::{Method, Request, RequestBuilder, RequestContent}, }; -use diesel_models::enums; use error_stack::{report, ResultExt}; -use masking::PeekInterface; -use transformers as airwallex; - -use super::utils::{self as connector_utils, AccessTokenRequestInfo, RefundsRequestData}; -use crate::{ - configs::settings, - core::{ - errors::{self, CustomResult}, - payments, +use hyperswitch_domain_models::{ + router_data::{AccessToken, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + CompleteAuthorize, PreProcessing, }, - events::connector_api_logs::ConnectorEvent, - headers, logger, - services::{ - self, - request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, + router_request_types::{ + AccessTokenRequestData, CompleteAuthorizeData, PaymentMethodTokenizationData, + PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsPreProcessingData, + PaymentsSessionData, PaymentsSyncData, RefundsData, SetupMandateRequestData, }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, types::{ - self, - api::{self, ConnectorCommon, ConnectorCommonExt}, - ErrorResponse, Response, RouterData, + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsCompleteAuthorizeRouterData, PaymentsPreProcessingRouterData, + PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, SetupMandateRouterData, }, - utils::{crypto, BytesExt}, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorRedirectResponse, + ConnectorSpecifications, ConnectorValidation, + }, + configs::Connectors, + disputes::DisputePayload, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, +}; +use masking::{Mask, PeekInterface}; +use router_env::logger; +use transformers as airwallex; + +use crate::{ + constants::headers, + types::{RefreshTokenRouterData, ResponseRouterData}, + utils::{construct_not_supported_error_report, AccessTokenRequestInfo, RefundsRequestData}, }; #[derive(Debug, Clone)] @@ -42,8 +63,8 @@ where fn build_headers( &self, req: &RouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let mut headers = vec![( headers::CONTENT_TYPE.to_string(), self.get_content_type().to_string().into(), @@ -76,7 +97,7 @@ impl ConnectorCommon for Airwallex { "application/json" } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.airwallex.base_url.as_ref() } @@ -106,16 +127,19 @@ impl ConnectorCommon for Airwallex { } impl ConnectorValidation for Airwallex { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( - connector_utils::construct_not_supported_error_report(capture_method, self.id()), + construct_not_supported_error_report(capture_method, self.id()), ), } } @@ -125,18 +149,14 @@ impl api::Payment for Airwallex {} impl api::PaymentsPreProcessing for Airwallex {} impl api::PaymentsCompleteAuthorize for Airwallex {} impl api::MandateSetup for Airwallex {} -impl - ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Airwallex +impl ConnectorIntegration + for Airwallex { fn build_request( &self, - _req: &types::SetupMandateRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &SetupMandateRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err( errors::ConnectorError::NotImplemented("Setup Mandate flow for Airwallex".to_string()) .into(), @@ -146,25 +166,19 @@ impl impl api::PaymentToken for Airwallex {} -impl - ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Airwallex +impl ConnectorIntegration + for Airwallex { // Not Implemented (R) } impl api::ConnectorAccessToken for Airwallex {} -impl ConnectorIntegration - for Airwallex -{ +impl ConnectorIntegration for Airwallex { fn get_url( &self, - _req: &types::RefreshTokenRouterData, - connectors: &settings::Connectors, + _req: &RefreshTokenRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!( "{}{}", @@ -175,9 +189,9 @@ impl ConnectorIntegration CustomResult)>, errors::ConnectorError> { + req: &RefreshTokenRouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let headers = vec![ ( headers::X_API_KEY.to_string(), @@ -194,12 +208,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &RefreshTokenRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { let req = Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .attach_default_headers() .headers(types::RefreshTokenType::get_headers(self, req, connectors)?) .url(&types::RefreshTokenType::get_url(self, req, connectors)?) @@ -210,10 +224,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { logger::debug!(access_token_response=?res); let response: airwallex::AirwallexAuthUpdateResponse = res .response @@ -223,7 +237,7 @@ impl ConnectorIntegration for Airwallex +impl ConnectorIntegration + for Airwallex { fn get_headers( &self, - req: &types::PaymentsPreProcessingRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsPreProcessingRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -261,8 +271,8 @@ impl fn get_url( &self, - _req: &types::PaymentsPreProcessingRouterData, - connectors: &settings::Connectors, + _req: &PaymentsPreProcessingRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!( "{}{}", @@ -273,8 +283,8 @@ impl fn get_request_body( &self, - req: &types::PaymentsPreProcessingRouterData, - _connectors: &settings::Connectors, + req: &PaymentsPreProcessingRouterData, + _connectors: &Connectors, ) -> CustomResult { let req_obj = airwallex::AirwallexIntentRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(req_obj))) @@ -282,12 +292,12 @@ impl fn build_request( &self, - req: &types::PaymentsPreProcessingRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsPreProcessingRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsPreProcessingType::get_url( self, req, connectors, )?) @@ -304,10 +314,10 @@ impl fn handle_response( &self, - data: &types::PaymentsPreProcessingRouterData, + data: &PaymentsPreProcessingRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: airwallex::AirwallexPaymentsResponse = res .response .parse_struct("airwallex AirwallexPaymentsResponse") @@ -316,7 +326,7 @@ impl event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -336,14 +346,12 @@ impl impl api::PaymentAuthorize for Airwallex {} #[async_trait::async_trait] -impl ConnectorIntegration - for Airwallex -{ +impl ConnectorIntegration for Airwallex { fn get_headers( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -353,8 +361,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}{}{}{}", @@ -369,8 +377,8 @@ impl ConnectorIntegration CustomResult { let connector_router_data = airwallex::AirwallexRouterData::try_from(( &self.get_currency_unit(), @@ -384,12 +392,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsAuthorizeType::get_url( self, req, connectors, )?) @@ -406,17 +414,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: airwallex::AirwallexPaymentsResponse = res .response .parse_struct("AirwallexPaymentsResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -434,14 +442,12 @@ impl ConnectorIntegration - for Airwallex -{ +impl ConnectorIntegration for Airwallex { fn get_headers( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -451,8 +457,8 @@ impl ConnectorIntegration CustomResult { let connector_payment_id = req .request @@ -469,12 +475,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) + RequestBuilder::new() + .method(Method::Get) .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) @@ -484,10 +490,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { logger::debug!(payment_sync_response=?res); let response: airwallex::AirwallexPaymentsSyncResponse = res .response @@ -497,7 +503,7 @@ impl ConnectorIntegration for Airwallex +impl ConnectorIntegration + for Airwallex { fn get_headers( &self, - req: &types::PaymentsCompleteAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } fn get_content_type(&self) -> &'static str { @@ -533,8 +535,8 @@ impl } fn get_url( &self, - req: &types::PaymentsCompleteAuthorizeRouterData, - connectors: &settings::Connectors, + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, ) -> CustomResult { let connector_payment_id = req .request @@ -549,8 +551,8 @@ impl } fn get_request_body( &self, - req: &types::PaymentsCompleteAuthorizeRouterData, - _connectors: &settings::Connectors, + req: &PaymentsCompleteAuthorizeRouterData, + _connectors: &Connectors, ) -> CustomResult { let req_obj = airwallex::AirwallexCompleteRequest::try_from(req)?; @@ -558,12 +560,12 @@ impl } fn build_request( &self, - req: &types::PaymentsCompleteAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsCompleteAuthorizeType::get_url( self, req, connectors, )?) @@ -578,17 +580,17 @@ impl } fn handle_response( &self, - data: &types::PaymentsCompleteAuthorizeRouterData, + data: &PaymentsCompleteAuthorizeRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: airwallex::AirwallexPaymentsResponse = res .response .parse_struct("AirwallexPaymentsResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -606,14 +608,12 @@ impl } impl api::PaymentCapture for Airwallex {} -impl ConnectorIntegration - for Airwallex -{ +impl ConnectorIntegration for Airwallex { fn get_headers( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -623,8 +623,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}{}{}{}", @@ -637,8 +637,8 @@ impl ConnectorIntegration CustomResult { let connector_req = airwallex::AirwallexPaymentsCaptureRequest::try_from(req)?; @@ -647,12 +647,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsCaptureType::get_headers( @@ -667,17 +667,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: airwallex::AirwallexPaymentsResponse = res .response .parse_struct("Airwallex PaymentsResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -696,22 +696,18 @@ impl ConnectorIntegration - for Airwallex -{ +impl ConnectorIntegration for Airwallex { //TODO: implement sessions flow } impl api::PaymentVoid for Airwallex {} -impl ConnectorIntegration - for Airwallex -{ +impl ConnectorIntegration for Airwallex { fn get_headers( &self, - req: &types::PaymentsCancelRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -721,8 +717,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}{}{}{}", @@ -734,8 +730,8 @@ impl ConnectorIntegration CustomResult { let connector_req = airwallex::AirwallexPaymentsCancelRequest::try_from(req)?; @@ -743,17 +739,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: airwallex::AirwallexPaymentsResponse = res .response .parse_struct("Airwallex PaymentsResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -763,12 +759,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) @@ -792,14 +788,12 @@ impl api::Refund for Airwallex {} impl api::RefundExecute for Airwallex {} impl api::RefundSync for Airwallex {} -impl ConnectorIntegration - for Airwallex -{ +impl ConnectorIntegration for Airwallex { fn get_headers( &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -809,8 +803,8 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, + _req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!( "{}{}", @@ -821,8 +815,8 @@ impl ConnectorIntegration, - _connectors: &settings::Connectors, + req: &RefundsRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_router_data = airwallex::AirwallexRouterData::try_from(( &self.get_currency_unit(), @@ -836,11 +830,11 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) .url(&types::RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundExecuteType::get_headers( @@ -855,10 +849,10 @@ impl ConnectorIntegration, + data: &RefundsRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult, errors::ConnectorError> { logger::debug!(target: "router::connector::airwallex", response=?res); let response: airwallex::RefundResponse = res .response @@ -866,7 +860,7 @@ impl ConnectorIntegration - for Airwallex -{ +impl ConnectorIntegration for Airwallex { fn get_headers( &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -900,8 +892,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}{}{}", @@ -913,12 +905,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) + RequestBuilder::new() + .method(Method::Get) .url(&types::RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundSyncType::get_headers(self, req, connectors)?) @@ -928,10 +920,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { logger::debug!(target: "router::connector::airwallex", response=?res); let response: airwallex::RefundResponse = res .response @@ -939,7 +931,7 @@ impl ConnectorIntegration, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Ok(Box::new(crypto::HmacSha256)) } fn get_webhook_source_verification_signature( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { let security_header = request @@ -987,7 +979,7 @@ impl api::IncomingWebhook for Airwallex { fn get_webhook_source_verification_message( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, _merchant_id: &common_utils::id_type::MerchantId, _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { @@ -1007,7 +999,7 @@ impl api::IncomingWebhook for Airwallex { fn get_webhook_object_reference_id( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult { let details: airwallex::AirwallexWebhookData = request .body @@ -1049,18 +1041,18 @@ impl api::IncomingWebhook for Airwallex { fn get_webhook_event_type( &self, - request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { let details: airwallex::AirwallexWebhookData = request .body .parse_struct("airwallexWebhookData") .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; - Ok(api::IncomingWebhookEvent::try_from(details.name)?) + Ok(IncomingWebhookEvent::try_from(details.name)?) } fn get_webhook_resource_object( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { let details: airwallex::AirwallexWebhookObjectResource = request .body @@ -1072,8 +1064,8 @@ impl api::IncomingWebhook for Airwallex { fn get_dispute_details( &self, - request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { let details: airwallex::AirwallexWebhookData = request .body .parse_struct("airwallexWebhookData") @@ -1083,7 +1075,7 @@ impl api::IncomingWebhook for Airwallex { .object .parse_value("AirwallexDisputeObject") .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; - Ok(api::disputes::DisputePayload { + Ok(DisputePayload { amount: dispute_details.dispute_amount.to_string(), currency: dispute_details.dispute_currency, dispute_stage: api_models::enums::DisputeStage::from(dispute_details.stage.clone()), @@ -1098,19 +1090,21 @@ impl api::IncomingWebhook for Airwallex { } } -impl services::ConnectorRedirectResponse for Airwallex { +impl ConnectorRedirectResponse for Airwallex { fn get_flow_type( &self, _query_params: &str, _json_payload: Option, - action: services::PaymentAction, - ) -> CustomResult { + action: PaymentAction, + ) -> CustomResult { match action { - services::PaymentAction::PSync - | services::PaymentAction::CompleteAuthorize - | services::PaymentAction::PaymentAuthenticateCompleteAuthorize => { - Ok(payments::CallConnectorAction::Trigger) + PaymentAction::PSync + | PaymentAction::CompleteAuthorize + | PaymentAction::PaymentAuthenticateCompleteAuthorize => { + Ok(CallConnectorAction::Trigger) } } } } + +impl ConnectorSpecifications for Airwallex {} diff --git a/crates/router/src/connector/airwallex/transformers.rs b/crates/hyperswitch_connectors/src/connectors/airwallex/transformers.rs similarity index 78% rename from crates/router/src/connector/airwallex/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/airwallex/transformers.rs index 4d84f64534e8..075a7b070d6d 100644 --- a/crates/router/src/connector/airwallex/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/airwallex/transformers.rs @@ -1,16 +1,27 @@ +use common_enums::enums; +use common_utils::{errors::ParsingError, pii::IpAddress, request::Method}; use error_stack::ResultExt; -use masking::{ExposeInterface, PeekInterface}; +use hyperswitch_domain_models::{ + payment_method_data::{PaymentMethodData, WalletData}, + router_data::{AccessToken, ConnectorAuthType, RouterData}, + router_flow_types::{ + refunds::{Execute, RSync}, + PSync, + }, + router_request_types::{PaymentsSyncData, ResponseId}, + router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData}, + types, +}; +use hyperswitch_interfaces::{api, errors}; +use masking::{ExposeInterface, PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; use url::Url; use uuid::Uuid; use crate::{ - connector::utils::{self, CardData}, - core::errors, - pii::Secret, - services, - types::{self, api, domain, storage::enums, PaymentsSyncData}, + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::{self, BrowserInformationData, CardData as _, PaymentsAuthorizeRequestData}, }; pub struct AirwallexAuthType { @@ -18,11 +29,11 @@ pub struct AirwallexAuthType { pub x_client_id: Secret, } -impl TryFrom<&types::ConnectorAuthType> for AirwallexAuthType { +impl TryFrom<&ConnectorAuthType> for AirwallexAuthType { type Error = error_stack::Report; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { - if let types::ConnectorAuthType::BodyKey { api_key, key1 } = auth_type { + fn try_from(auth_type: &ConnectorAuthType) -> Result { + if let ConnectorAuthType::BodyKey { api_key, key1 } = auth_type { Ok(Self { x_api_key: api_key.clone(), x_client_id: key1.clone(), @@ -32,6 +43,14 @@ impl TryFrom<&types::ConnectorAuthType> for AirwallexAuthType { } } } + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct ReferrerData { + #[serde(rename = "type")] + r_type: String, + version: String, +} + #[derive(Default, Debug, Serialize, Eq, PartialEq)] pub struct AirwallexIntentRequest { // Unique ID to be sent for each transaction/operation request to the connector @@ -40,10 +59,16 @@ pub struct AirwallexIntentRequest { currency: enums::Currency, //ID created in merchant's order system that corresponds to this PaymentIntent. merchant_order_id: String, + // This data is required to whitelist Hyperswitch at Airwallex. + referrer_data: ReferrerData, } impl TryFrom<&types::PaymentsPreProcessingRouterData> for AirwallexIntentRequest { type Error = error_stack::Report; fn try_from(item: &types::PaymentsPreProcessingRouterData) -> Result { + let referrer_data = ReferrerData { + r_type: "hyperswitch".to_string(), + version: "1.0.0".to_string(), + }; // amount and currency will always be Some since PaymentsPreProcessingData is constructed using PaymentsAuthorizeData let amount = item .request @@ -62,6 +87,7 @@ impl TryFrom<&types::PaymentsPreProcessingRouterData> for AirwallexIntentRequest amount: utils::to_currency_base_unit(amount, currency)?, currency, merchant_order_id: item.connector_request_reference_id.clone(), + referrer_data, }) } } @@ -98,13 +124,47 @@ pub struct AirwallexPaymentsRequest { payment_method: AirwallexPaymentMethod, payment_method_options: Option, return_url: Option, + device_data: DeviceData, +} + +#[derive(Debug, Serialize)] +pub struct DeviceData { + accept_header: String, + browser: Browser, + ip_address: Secret, + language: String, + mobile: Option, + screen_color_depth: u8, + screen_height: u32, + screen_width: u32, + timezone: String, +} + +#[derive(Debug, Serialize)] +pub struct Browser { + java_enabled: bool, + javascript_enabled: bool, + user_agent: String, +} + +#[derive(Debug, Serialize)] +pub struct Location { + lat: String, + lon: String, +} + +#[derive(Debug, Serialize)] +pub struct Mobile { + device_model: Option, + os_type: Option, + os_version: Option, } #[derive(Debug, Serialize)] #[serde(untagged)] pub enum AirwallexPaymentMethod { Card(AirwallexCard), - Wallets(WalletData), + Wallets(AirwallexWalletData), } #[derive(Debug, Serialize)] @@ -123,7 +183,7 @@ pub struct AirwallexCardDetails { #[derive(Debug, Serialize)] #[serde(untagged)] -pub enum WalletData { +pub enum AirwallexWalletData { GooglePay(GooglePayData), } @@ -173,12 +233,14 @@ impl TryFrom<&AirwallexRouterData<&types::PaymentsAuthorizeRouterData>> let mut payment_method_options = None; let request = &item.router_data.request; let payment_method = match request.payment_method_data.clone() { - domain::PaymentMethodData::Card(ccard) => { + PaymentMethodData::Card(ccard) => { payment_method_options = Some(AirwallexPaymentOptions::Card(AirwallexCardPaymentOptions { auto_capture: matches!( request.capture_method, - Some(enums::CaptureMethod::Automatic) | None + Some(enums::CaptureMethod::Automatic) + | Some(enums::CaptureMethod::SequentialAutomatic) + | None ), })); Ok(AirwallexPaymentMethod::Card(AirwallexCard { @@ -191,43 +253,84 @@ impl TryFrom<&AirwallexRouterData<&types::PaymentsAuthorizeRouterData>> payment_method_type: AirwallexPaymentType::Card, })) } - domain::PaymentMethodData::Wallet(ref wallet_data) => get_wallet_details(wallet_data), - domain::PaymentMethodData::PayLater(_) - | domain::PaymentMethodData::BankRedirect(_) - | domain::PaymentMethodData::BankDebit(_) - | domain::PaymentMethodData::BankTransfer(_) - | domain::PaymentMethodData::CardRedirect(_) - | domain::PaymentMethodData::Crypto(_) - | domain::PaymentMethodData::MandatePayment - | domain::PaymentMethodData::Reward - | domain::PaymentMethodData::RealTimePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + PaymentMethodData::Wallet(ref wallet_data) => get_wallet_details(wallet_data), + PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::CardRedirect(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("airwallex"), )) } }?; + let device_data = get_device_data(item.router_data)?; Ok(Self { request_id: Uuid::new_v4().to_string(), payment_method, payment_method_options, return_url: request.complete_authorize_url.clone(), + device_data, }) } } +fn get_device_data( + item: &types::PaymentsAuthorizeRouterData, +) -> Result> { + let info = item.request.get_browser_info()?; + let browser = Browser { + java_enabled: info.get_java_enabled()?, + javascript_enabled: info.get_java_script_enabled()?, + user_agent: info.get_user_agent()?, + }; + let mobile = { + let device_model = info.get_device_model().ok(); + let os_type = info.get_os_type().ok(); + let os_version = info.get_os_version().ok(); + + if device_model.is_some() || os_type.is_some() || os_version.is_some() { + Some(Mobile { + device_model, + os_type, + os_version, + }) + } else { + None + } + }; + Ok(DeviceData { + accept_header: info.get_accept_header()?, + browser, + ip_address: info.get_ip_address()?, + mobile, + screen_color_depth: info.get_color_depth()?, + screen_height: info.get_screen_height()?, + screen_width: info.get_screen_width()?, + timezone: info.get_time_zone()?.to_string(), + language: info.get_language()?, + }) +} + fn get_wallet_details( - wallet_data: &domain::WalletData, + wallet_data: &WalletData, ) -> Result { let wallet_details: AirwallexPaymentMethod = match wallet_data { - domain::WalletData::GooglePay(gpay_details) => { - AirwallexPaymentMethod::Wallets(WalletData::GooglePay(GooglePayData { + WalletData::GooglePay(gpay_details) => { + AirwallexPaymentMethod::Wallets(AirwallexWalletData::GooglePay(GooglePayData { googlepay: GooglePayDetails { encrypted_payment_token: Secret::new( gpay_details.tokenization_data.token.clone(), @@ -237,32 +340,33 @@ fn get_wallet_details( payment_method_type: AirwallexPaymentType::Googlepay, })) } - domain::WalletData::AliPayQr(_) - | domain::WalletData::AliPayRedirect(_) - | domain::WalletData::AliPayHkRedirect(_) - | domain::WalletData::MomoRedirect(_) - | domain::WalletData::KakaoPayRedirect(_) - | domain::WalletData::GoPayRedirect(_) - | domain::WalletData::GcashRedirect(_) - | domain::WalletData::ApplePay(_) - | domain::WalletData::ApplePayRedirect(_) - | domain::WalletData::ApplePayThirdPartySdk(_) - | domain::WalletData::DanaRedirect {} - | domain::WalletData::GooglePayRedirect(_) - | domain::WalletData::GooglePayThirdPartySdk(_) - | domain::WalletData::MbWayRedirect(_) - | domain::WalletData::MobilePayRedirect(_) - | domain::WalletData::PaypalRedirect(_) - | domain::WalletData::PaypalSdk(_) - | domain::WalletData::SamsungPay(_) - | domain::WalletData::TwintRedirect {} - | domain::WalletData::VippsRedirect {} - | domain::WalletData::TouchNGoRedirect(_) - | domain::WalletData::WeChatPayRedirect(_) - | domain::WalletData::WeChatPayQr(_) - | domain::WalletData::CashappQr(_) - | domain::WalletData::SwishQr(_) - | domain::WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( + WalletData::AliPayQr(_) + | WalletData::AliPayRedirect(_) + | WalletData::AliPayHkRedirect(_) + | WalletData::MomoRedirect(_) + | WalletData::KakaoPayRedirect(_) + | WalletData::GoPayRedirect(_) + | WalletData::GcashRedirect(_) + | WalletData::ApplePay(_) + | WalletData::ApplePayRedirect(_) + | WalletData::ApplePayThirdPartySdk(_) + | WalletData::DanaRedirect {} + | WalletData::GooglePayRedirect(_) + | WalletData::GooglePayThirdPartySdk(_) + | WalletData::MbWayRedirect(_) + | WalletData::MobilePayRedirect(_) + | WalletData::PaypalRedirect(_) + | WalletData::PaypalSdk(_) + | WalletData::Paze(_) + | WalletData::SamsungPay(_) + | WalletData::TwintRedirect {} + | WalletData::VippsRedirect {} + | WalletData::TouchNGoRedirect(_) + | WalletData::WeChatPayRedirect(_) + | WalletData::WeChatPayQr(_) + | WalletData::CashappQr(_) + | WalletData::SwishQr(_) + | WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("airwallex"), ))?, }; @@ -276,16 +380,16 @@ pub struct AirwallexAuthUpdateResponse { token: Secret, } -impl TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { let expires = (item.response.expires_at - common_utils::date_time::now()).whole_seconds(); Ok(Self { - response: Ok(types::AccessToken { + response: Ok(AccessToken { token: item.response.token, expires, }), @@ -436,7 +540,7 @@ pub struct AirwallexRedirectFormData { #[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] pub struct AirwallexPaymentsNextAction { url: Url, - method: services::Method, + method: Method, data: AirwallexRedirectFormData, stage: AirwallexNextActionStage, } @@ -463,10 +567,8 @@ pub struct AirwallexPaymentsSyncResponse { next_action: Option, } -fn get_redirection_form( - response_url_data: AirwallexPaymentsNextAction, -) -> Option { - Some(services::RedirectForm::Form { +fn get_redirection_form(response_url_data: AirwallexPaymentsNextAction) -> Option { + Some(RedirectForm::Form { endpoint: response_url_data.url.to_string(), method: response_url_data.method, form_fields: std::collections::HashMap::from([ @@ -507,18 +609,12 @@ fn get_redirection_form( }) } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< - F, - AirwallexPaymentsResponse, - T, - types::PaymentsResponseData, - >, + item: ResponseRouterData, ) -> Result { let (status, redirection_data) = item.response.next_action.clone().map_or( // If no next action is there, map the status and set redirection form as None @@ -566,13 +662,13 @@ impl Ok(Self { status, reference_id: Some(item.response.id.clone()), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(item.response.id), - redirection_data, - mandate_reference: None, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.id.clone()), + redirection_data: Box::new(redirection_data), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: Some(item.response.id), incremental_authorization_allowed: None, charge_id: None, }), @@ -583,21 +679,21 @@ impl impl TryFrom< - types::ResponseRouterData< - api::PSync, + ResponseRouterData< + PSync, AirwallexPaymentsSyncResponse, PaymentsSyncData, - types::PaymentsResponseData, + PaymentsResponseData, >, > for types::PaymentsSyncRouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< - api::PSync, + item: ResponseRouterData< + PSync, AirwallexPaymentsSyncResponse, PaymentsSyncData, - types::PaymentsResponseData, + PaymentsResponseData, >, ) -> Result { let status = get_payment_status(&item.response.status, &item.response.next_action); @@ -609,13 +705,13 @@ impl Ok(Self { status, reference_id: Some(item.response.id.clone()), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(item.response.id), - redirection_data, - mandate_reference: None, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.id.clone()), + redirection_data: Box::new(redirection_data), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: Some(item.response.id), incremental_authorization_allowed: None, charge_id: None, }), @@ -680,16 +776,16 @@ pub struct RefundResponse { status: RefundStatus, } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for types::RefundsRouterData { - type Error = error_stack::Report; + type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { let refund_status = enums::RefundStatus::from(item.response.status); Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item.response.id, refund_status, }), @@ -698,16 +794,14 @@ impl TryFrom> } } -impl TryFrom> - for types::RefundsRouterData -{ - type Error = error_stack::Report; +impl TryFrom> for types::RefundsRouterData { + type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { let refund_status = enums::RefundStatus::from(item.response.status); Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item.response.id, refund_status, }), @@ -825,7 +919,7 @@ pub struct AirwallexObjectData { pub struct AirwallexDisputeObject { pub payment_intent_id: String, pub dispute_amount: i64, - pub dispute_currency: String, + pub dispute_currency: enums::Currency, pub stage: AirwallexDisputeStage, pub dispute_id: String, pub dispute_reason_type: Option, diff --git a/crates/hyperswitch_connectors/src/connectors/amazonpay.rs b/crates/hyperswitch_connectors/src/connectors/amazonpay.rs new file mode 100644 index 000000000000..f89595415748 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/amazonpay.rs @@ -0,0 +1,572 @@ +pub mod transformers; + +use common_utils::{ + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, +}; +use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::{ + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, + RefundSyncRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks, +}; +use masking::{ExposeInterface, Mask}; +use transformers as amazonpay; + +use crate::{constants::headers, types::ResponseRouterData, utils}; + +#[derive(Clone)] +pub struct Amazonpay { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl Amazonpay { + pub fn new() -> &'static Self { + &Self { + amount_converter: &StringMinorUnitForConnector, + } + } +} + +impl api::Payment for Amazonpay {} +impl api::PaymentSession for Amazonpay {} +impl api::ConnectorAccessToken for Amazonpay {} +impl api::MandateSetup for Amazonpay {} +impl api::PaymentAuthorize for Amazonpay {} +impl api::PaymentSync for Amazonpay {} +impl api::PaymentCapture for Amazonpay {} +impl api::PaymentVoid for Amazonpay {} +impl api::Refund for Amazonpay {} +impl api::RefundExecute for Amazonpay {} +impl api::RefundSync for Amazonpay {} +impl api::PaymentToken for Amazonpay {} + +impl ConnectorIntegration + for Amazonpay +{ + // Not Implemented (R) +} + +impl ConnectorCommonExt for Amazonpay +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } +} + +impl ConnectorCommon for Amazonpay { + fn id(&self) -> &'static str { + "amazonpay" + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Base + // todo!() + // TODO! Check connector documentation, on which unit they are processing the currency. + // If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor, + // if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { + connectors.amazonpay.base_url.as_ref() + } + + fn get_auth_header( + &self, + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + let auth = amazonpay::AmazonpayAuthType::try_from(auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + Ok(vec![( + headers::AUTHORIZATION.to_string(), + auth.api_key.expose().into_masked(), + )]) + } + + fn build_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: amazonpay::AmazonpayErrorResponse = res + .response + .parse_struct("AmazonpayErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.code, + message: response.message, + reason: response.reason, + attempt_status: None, + connector_transaction_id: None, + }) + } +} + +impl ConnectorValidation for Amazonpay { + //TODO: implement functions when support enabled +} + +impl ConnectorIntegration for Amazonpay { + //TODO: implement sessions flow +} + +impl ConnectorIntegration for Amazonpay {} + +impl ConnectorIntegration + for Amazonpay +{ +} + +impl ConnectorIntegration for Amazonpay { + fn get_headers( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + + let connector_router_data = amazonpay::AmazonpayRouterData::from((amount, req)); + let connector_req = amazonpay::AmazonpayPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: amazonpay::AmazonpayPaymentsResponse = res + .response + .parse_struct("Amazonpay PaymentsAuthorizeResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Amazonpay { + fn get_headers( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: amazonpay::AmazonpayPaymentsResponse = res + .response + .parse_struct("amazonpay PaymentsSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Amazonpay { + fn get_headers( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsCaptureType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCaptureType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCaptureRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: amazonpay::AmazonpayPaymentsResponse = res + .response + .parse_struct("Amazonpay PaymentsCaptureResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Amazonpay {} + +impl ConnectorIntegration for Amazonpay { + fn get_headers( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let refund_amount = utils::convert_amount( + self.amount_converter, + req.request.minor_refund_amount, + req.request.currency, + )?; + + let connector_router_data = amazonpay::AmazonpayRouterData::from((refund_amount, req)); + let connector_req = amazonpay::AmazonpayRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundExecuteType::get_headers( + self, req, connectors, + )?) + .set_body(types::RefundExecuteType::get_request_body( + self, req, connectors, + )?) + .build(); + Ok(Some(request)) + } + + fn handle_response( + &self, + data: &RefundsRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult, errors::ConnectorError> { + let response: amazonpay::RefundResponse = res + .response + .parse_struct("amazonpay RefundResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Amazonpay { + fn get_headers( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &RefundSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::RefundSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundSyncType::get_headers(self, req, connectors)?) + .set_body(types::RefundSyncType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &RefundSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: amazonpay::RefundResponse = res + .response + .parse_struct("amazonpay RefundSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +#[async_trait::async_trait] +impl webhooks::IncomingWebhook for Amazonpay { + fn get_webhook_object_reference_id( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_event_type( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_resource_object( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } +} + +impl ConnectorSpecifications for Amazonpay {} diff --git a/crates/hyperswitch_connectors/src/connectors/amazonpay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/amazonpay/transformers.rs new file mode 100644 index 000000000000..b61327814237 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/amazonpay/transformers.rs @@ -0,0 +1,228 @@ +use common_enums::enums; +use common_utils::types::StringMinorUnit; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{ConnectorAuthType, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{PaymentsAuthorizeRouterData, RefundsRouterData}, +}; +use hyperswitch_interfaces::errors; +use masking::Secret; +use serde::{Deserialize, Serialize}; + +use crate::{ + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::PaymentsAuthorizeRequestData, +}; + +//TODO: Fill the struct with respective fields +pub struct AmazonpayRouterData { + pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub router_data: T, +} + +impl From<(StringMinorUnit, T)> for AmazonpayRouterData { + fn from((amount, item): (StringMinorUnit, T)) -> Self { + //Todo : use utils to convert the amount to the type of amount that a connector accepts + Self { + amount, + router_data: item, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, PartialEq)] +pub struct AmazonpayPaymentsRequest { + amount: StringMinorUnit, + card: AmazonpayCard, +} + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct AmazonpayCard { + number: cards::CardNumber, + expiry_month: Secret, + expiry_year: Secret, + cvc: Secret, + complete: bool, +} + +impl TryFrom<&AmazonpayRouterData<&PaymentsAuthorizeRouterData>> for AmazonpayPaymentsRequest { + type Error = error_stack::Report; + fn try_from( + item: &AmazonpayRouterData<&PaymentsAuthorizeRouterData>, + ) -> Result { + match item.router_data.request.payment_method_data.clone() { + PaymentMethodData::Card(req_card) => { + let card = AmazonpayCard { + number: req_card.card_number, + expiry_month: req_card.card_exp_month, + expiry_year: req_card.card_exp_year, + cvc: req_card.card_cvc, + complete: item.router_data.request.is_auto_capture()?, + }; + Ok(Self { + amount: item.amount.clone(), + card, + }) + } + _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), + } + } +} + +//TODO: Fill the struct with respective fields +// Auth Struct +pub struct AmazonpayAuthType { + pub(super) api_key: Secret, +} + +impl TryFrom<&ConnectorAuthType> for AmazonpayAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &ConnectorAuthType) -> Result { + match auth_type { + ConnectorAuthType::HeaderKey { api_key } => Ok(Self { + api_key: api_key.to_owned(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} +// PaymentsResponse +//TODO: Append the remaining status flags +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum AmazonpayPaymentStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for common_enums::AttemptStatus { + fn from(item: AmazonpayPaymentStatus) -> Self { + match item { + AmazonpayPaymentStatus::Succeeded => Self::Charged, + AmazonpayPaymentStatus::Failed => Self::Failure, + AmazonpayPaymentStatus::Processing => Self::Authorizing, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct AmazonpayPaymentsResponse { + status: AmazonpayPaymentStatus, + id: String, +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + Ok(Self { + status: common_enums::AttemptStatus::from(item.response.status), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +// REFUND : +// Type definition for RefundRequest +#[derive(Default, Debug, Serialize)] +pub struct AmazonpayRefundRequest { + pub amount: StringMinorUnit, +} + +impl TryFrom<&AmazonpayRouterData<&RefundsRouterData>> for AmazonpayRefundRequest { + type Error = error_stack::Report; + fn try_from(item: &AmazonpayRouterData<&RefundsRouterData>) -> Result { + Ok(Self { + amount: item.amount.to_owned(), + }) + } +} + +// Type definition for Refund Response + +#[allow(dead_code)] +#[derive(Debug, Serialize, Default, Deserialize, Clone)] +pub enum RefundStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for enums::RefundStatus { + fn from(item: RefundStatus) -> Self { + match item { + RefundStatus::Succeeded => Self::Success, + RefundStatus::Failed => Self::Failure, + RefundStatus::Processing => Self::Pending, + //TODO: Review mapping + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct RefundResponse { + id: String, + status: RefundStatus, +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct AmazonpayErrorResponse { + pub status_code: u16, + pub code: String, + pub message: String, + pub reason: Option, +} diff --git a/crates/hyperswitch_connectors/src/connectors/bambora.rs b/crates/hyperswitch_connectors/src/connectors/bambora.rs index 4731546b3d33..193a16df0b19 100644 --- a/crates/hyperswitch_connectors/src/connectors/bambora.rs +++ b/crates/hyperswitch_connectors/src/connectors/bambora.rs @@ -22,7 +22,10 @@ use hyperswitch_domain_models::{ PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, RefundsData, SetupMandateRequestData, }, - router_response_types::{PaymentsResponseData, RefundsResponseData}, + router_response_types::{ + ConnectorInfo, PaymentMethodDetails, PaymentsResponseData, RefundsResponseData, + SupportedPaymentMethods, SupportedPaymentMethodsExt, + }, types::{ PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, PaymentsCompleteAuthorizeRouterData, PaymentsSyncRouterData, RefundSyncRouterData, @@ -32,7 +35,7 @@ use hyperswitch_domain_models::{ use hyperswitch_interfaces::{ api::{ self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorRedirectResponse, - ConnectorValidation, + ConnectorSpecifications, ConnectorValidation, }, configs::Connectors, errors, @@ -43,6 +46,7 @@ use hyperswitch_interfaces::{ }, webhooks, }; +use lazy_static::lazy_static; use masking::Mask; use transformers as bambora; @@ -144,21 +148,7 @@ impl ConnectorCommon for Bambora { } } -impl ConnectorValidation for Bambora { - fn validate_capture_method( - &self, - capture_method: Option, - _pmt: Option, - ) -> CustomResult<(), errors::ConnectorError> { - let capture_method = capture_method.unwrap_or_default(); - match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), - enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( - utils::construct_not_implemented_error_report(capture_method, self.id()), - ), - } - } -} +impl ConnectorValidation for Bambora {} impl ConnectorIntegration for Bambora @@ -826,3 +816,56 @@ impl webhooks::IncomingWebhook for Bambora { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +lazy_static! { + static ref BAMBORA_SUPPORTED_PAYMENT_METHODS: SupportedPaymentMethods = { + let default_capture_methods = vec![ + enums::CaptureMethod::Automatic, + enums::CaptureMethod::Manual, + enums::CaptureMethod::SequentialAutomatic, + ]; + + let mut bambora_supported_payment_methods = SupportedPaymentMethods::new(); + + bambora_supported_payment_methods.add( + enums::PaymentMethod::Card, + enums::PaymentMethodType::Credit, + PaymentMethodDetails { + mandates: common_enums::FeatureStatus::NotSupported, + refunds: common_enums::FeatureStatus::Supported, + supported_capture_methods: default_capture_methods.clone(), + }, + ); + bambora_supported_payment_methods.add( + enums::PaymentMethod::Card, + enums::PaymentMethodType::Debit, + PaymentMethodDetails { + mandates: common_enums::FeatureStatus::NotSupported, + refunds: common_enums::FeatureStatus::Supported, + supported_capture_methods: default_capture_methods.clone(), + }, + ); + + bambora_supported_payment_methods + }; + static ref BAMBORA_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo { + description: "Bambora is a leading online payment provider in Canada and United States." + .to_string(), + connector_type: enums::PaymentConnectorCategory::PaymentGateway, + }; + static ref BAMBORA_SUPPORTED_WEBHOOK_FLOWS: Vec = Vec::new(); +} + +impl ConnectorSpecifications for Bambora { + fn get_connector_about(&self) -> Option<&'static ConnectorInfo> { + Some(&*BAMBORA_CONNECTOR_INFO) + } + + fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> { + Some(&*BAMBORA_SUPPORTED_PAYMENT_METHODS) + } + + fn get_supported_webhook_flows(&self) -> Option<&'static [enums::EventClass]> { + Some(&*BAMBORA_SUPPORTED_WEBHOOK_FLOWS) + } +} diff --git a/crates/hyperswitch_connectors/src/connectors/bambora/transformers.rs b/crates/hyperswitch_connectors/src/connectors/bambora/transformers.rs index b8c75e3c4dbb..f0ea149b0d6c 100644 --- a/crates/hyperswitch_connectors/src/connectors/bambora/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/bambora/transformers.rs @@ -221,13 +221,17 @@ impl TryFrom> for Bambora | PaymentMethodData::RealTimePayment(_) | PaymentMethodData::Upi(_) | PaymentMethodData::Voucher(_) + | PaymentMethodData::MobilePayment(_) | PaymentMethodData::GiftCard(_) | PaymentMethodData::OpenBanking(_) | PaymentMethodData::CardToken(_) - | PaymentMethodData::NetworkToken(_) => Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("bambora"), - ) - .into()), + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("bambora"), + ) + .into()) + } } } } @@ -468,8 +472,8 @@ impl TryFrom TryFrom }, response: Ok(PaymentsResponseData::TransactionResponse { resource_id: ResponseId::ConnectorTransactionId(item.response.id.to_string()), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(item.response.order_number.to_string()), @@ -583,8 +587,8 @@ impl }, response: Ok(PaymentsResponseData::TransactionResponse { resource_id: ResponseId::ConnectorTransactionId(item.response.id.to_string()), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(item.response.order_number.to_string()), @@ -618,8 +622,8 @@ impl }, response: Ok(PaymentsResponseData::TransactionResponse { resource_id: ResponseId::ConnectorTransactionId(item.response.id.to_string()), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(item.response.order_number.to_string()), @@ -653,8 +657,8 @@ impl }, response: Ok(PaymentsResponseData::TransactionResponse { resource_id: ResponseId::ConnectorTransactionId(item.response.id.to_string()), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(item.response.order_number.to_string()), diff --git a/crates/router/src/connector/bamboraapac.rs b/crates/hyperswitch_connectors/src/connectors/bamboraapac.rs similarity index 70% rename from crates/router/src/connector/bamboraapac.rs rename to crates/hyperswitch_connectors/src/connectors/bamboraapac.rs index 82ed134a9756..cdfacbf3ecb1 100644 --- a/crates/router/src/connector/bamboraapac.rs +++ b/crates/hyperswitch_connectors/src/connectors/bamboraapac.rs @@ -1,28 +1,51 @@ pub mod transformers; +use api_models::webhooks::{IncomingWebhookEvent, ObjectReferenceId}; +use common_enums::enums; use common_utils::{ - self, - ext_traits::XmlExt, + errors::CustomResult, + ext_traits::{BytesExt, XmlExt}, + request::{Method, Request, RequestBuilder, RequestContent}, types::{AmountConvertor, MinorUnit, MinorUnitForConnector}, }; -use diesel_models::enums; use error_stack::{report, Report, ResultExt}; -use hyperswitch_interfaces::consts; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{AccessToken, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, + RefundExecuteRouterData, RefundSyncRouterData, SetupMandateRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE}, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, +}; use transformers as bamboraapac; use crate::{ - configs::settings, - connector::utils as connector_utils, - core::errors::{self, CustomResult}, - events::connector_api_logs::ConnectorEvent, - headers, - services::{self, request, ConnectorIntegration, ConnectorValidation}, - types::{ - self, - api::{self, ConnectorCommon, ConnectorCommonExt}, - ErrorResponse, RequestContent, Response, - }, - utils::{self, BytesExt}, + constants::headers, + types::ResponseRouterData, + utils::{self, construct_not_implemented_error_report, convert_amount}, }; #[derive(Clone)] @@ -51,12 +74,8 @@ impl api::RefundExecute for Bamboraapac {} impl api::RefundSync for Bamboraapac {} impl api::PaymentToken for Bamboraapac {} -impl - ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Bamboraapac +impl ConnectorIntegration + for Bamboraapac { // Not Implemented (R) } @@ -67,9 +86,9 @@ where { fn build_headers( &self, - _req: &types::RouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let header = vec![( headers::CONTENT_TYPE.to_string(), self.get_content_type().to_string().into(), @@ -79,16 +98,19 @@ where } impl ConnectorValidation for Bamboraapac { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( - connector_utils::construct_not_implemented_error_report(capture_method, self.id()), + construct_not_implemented_error_report(capture_method, self.id()), ), } } @@ -96,11 +118,11 @@ impl ConnectorValidation for Bamboraapac { fn validate_mandate_payment( &self, _pm_type: Option, - pm_data: types::domain::payments::PaymentMethodData, + pm_data: PaymentMethodData, ) -> CustomResult<(), errors::ConnectorError> { let connector = self.id(); match pm_data { - types::domain::payments::PaymentMethodData::Card(_) => Ok(()), + PaymentMethodData::Card(_) => Ok(()), _ => Err(errors::ConnectorError::NotSupported { message: "mandate payment".to_string(), connector, @@ -123,7 +145,7 @@ impl ConnectorCommon for Bamboraapac { "text/xml" } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.bamboraapac.base_url.as_ref() } @@ -146,11 +168,11 @@ impl ConnectorCommon for Bamboraapac { status_code: res.status_code, code: response_data .declined_code - .unwrap_or(consts::NO_ERROR_CODE.to_string()), + .unwrap_or(NO_ERROR_CODE.to_string()), message: response_data .declined_message .clone() - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + .unwrap_or(NO_ERROR_MESSAGE.to_string()), reason: response_data.declined_message, attempt_status: None, connector_transaction_id: None, @@ -165,29 +187,20 @@ impl ConnectorCommon for Bamboraapac { } } -impl ConnectorIntegration - for Bamboraapac -{ +impl ConnectorIntegration for Bamboraapac { //TODO: implement sessions flow } -impl ConnectorIntegration - for Bamboraapac -{ -} +impl ConnectorIntegration for Bamboraapac {} -impl - ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Bamboraapac +impl ConnectorIntegration + for Bamboraapac { fn get_headers( &self, - req: &types::SetupMandateRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &SetupMandateRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -197,16 +210,16 @@ impl fn get_url( &self, - _req: &types::SetupMandateRouterData, - connectors: &settings::Connectors, + _req: &SetupMandateRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!("{}/sipp.asmx", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::SetupMandateRouterData, - _connectors: &settings::Connectors, + req: &SetupMandateRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_req = bamboraapac::get_setup_mandate_body(req)?; @@ -215,12 +228,12 @@ impl fn build_request( &self, - req: &types::SetupMandateRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &SetupMandateRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::SetupMandateType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::SetupMandateType::get_headers(self, req, connectors)?) @@ -233,10 +246,10 @@ impl fn handle_response( &self, - data: &types::SetupMandateRouterData, + data: &SetupMandateRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response_data = html_to_xml_string_conversion( String::from_utf8(res.response.to_vec()) .change_context(errors::ConnectorError::ResponseDeserializationFailed)?, @@ -248,7 +261,7 @@ impl event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -264,14 +277,12 @@ impl } } -impl ConnectorIntegration - for Bamboraapac -{ +impl ConnectorIntegration for Bamboraapac { fn get_headers( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -281,18 +292,18 @@ impl ConnectorIntegration CustomResult { Ok(format!("{}/dts.asmx", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::PaymentsAuthorizeRouterData, - _connectors: &settings::Connectors, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, ) -> CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_amount, req.request.currency, @@ -306,12 +317,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsAuthorizeType::get_url( self, req, connectors, )?) @@ -328,10 +339,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response_data = html_to_xml_string_conversion( String::from_utf8(res.response.to_vec()) .change_context(errors::ConnectorError::ResponseDeserializationFailed)?, @@ -343,7 +354,7 @@ impl ConnectorIntegration - for Bamboraapac -{ +impl ConnectorIntegration for Bamboraapac { fn get_headers( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -376,16 +385,16 @@ impl ConnectorIntegration CustomResult { Ok(format!("{}/dts.asmx", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::PaymentsSyncRouterData, - _connectors: &settings::Connectors, + req: &PaymentsSyncRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_req = bamboraapac::get_payment_sync_body(req)?; @@ -394,12 +403,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) @@ -412,10 +421,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response_data = html_to_xml_string_conversion( String::from_utf8(res.response.to_vec()) .change_context(errors::ConnectorError::ResponseDeserializationFailed)?, @@ -427,7 +436,7 @@ impl ConnectorIntegration - for Bamboraapac -{ +impl ConnectorIntegration for Bamboraapac { fn get_headers( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -460,18 +467,18 @@ impl ConnectorIntegration CustomResult { Ok(format!("{}/dts.asmx", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::PaymentsCaptureRouterData, - _connectors: &settings::Connectors, + req: &PaymentsCaptureRouterData, + _connectors: &Connectors, ) -> CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_amount_to_capture, req.request.currency, @@ -485,12 +492,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsCaptureType::get_headers( @@ -505,10 +512,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response_data = html_to_xml_string_conversion( String::from_utf8(res.response.to_vec()) .change_context(errors::ConnectorError::ResponseDeserializationFailed)?, @@ -521,7 +528,7 @@ impl ConnectorIntegration - for Bamboraapac -{ -} +impl ConnectorIntegration for Bamboraapac {} -impl ConnectorIntegration - for Bamboraapac -{ +impl ConnectorIntegration for Bamboraapac { fn get_headers( &self, - req: &types::RefundExecuteRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundExecuteRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -559,18 +561,18 @@ impl ConnectorIntegration CustomResult { Ok(format!("{}/dts.asmx", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::RefundExecuteRouterData, - _connectors: &settings::Connectors, + req: &RefundExecuteRouterData, + _connectors: &Connectors, ) -> CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_refund_amount, req.request.currency, @@ -584,11 +586,11 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) + req: &RefundExecuteRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) .url(&types::RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundExecuteType::get_headers( @@ -603,10 +605,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response_data = html_to_xml_string_conversion( String::from_utf8(res.response.to_vec()) .change_context(errors::ConnectorError::ResponseDeserializationFailed)?, @@ -619,7 +621,7 @@ impl ConnectorIntegration - for Bamboraapac -{ +impl ConnectorIntegration for Bamboraapac { fn get_headers( &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -652,16 +652,16 @@ impl ConnectorIntegration CustomResult { Ok(format!("{}/dts.asmx", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::RefundSyncRouterData, - _connectors: &settings::Connectors, + req: &RefundSyncRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_req = bamboraapac::get_refund_sync_body(req)?; @@ -670,12 +670,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundSyncType::get_headers(self, req, connectors)?) @@ -688,10 +688,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response_data = html_to_xml_string_conversion( String::from_utf8(res.response.to_vec()) .change_context(errors::ConnectorError::ResponseDeserializationFailed)?, @@ -703,7 +703,7 @@ impl ConnectorIntegration, - ) -> CustomResult { + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_event_type( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_resource_object( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } @@ -746,3 +746,5 @@ impl api::IncomingWebhook for Bamboraapac { fn html_to_xml_string_conversion(res: String) -> String { res.replace("<", "<").replace(">", ">") } + +impl ConnectorSpecifications for Bamboraapac {} diff --git a/crates/router/src/connector/bamboraapac/transformers.rs b/crates/hyperswitch_connectors/src/connectors/bamboraapac/transformers.rs similarity index 84% rename from crates/router/src/connector/bamboraapac/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/bamboraapac/transformers.rs index 819c918338e7..f4399d105c5d 100644 --- a/crates/router/src/connector/bamboraapac/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/bamboraapac/transformers.rs @@ -1,13 +1,26 @@ +use common_enums::enums; use common_utils::types::MinorUnit; use error_stack::ResultExt; -use hyperswitch_interfaces::consts; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{ConnectorAuthType, ErrorResponse, RouterData}, + router_request_types::{ + PaymentsAuthorizeData, PaymentsCaptureData, PaymentsSyncData, RefundsData, ResponseId, + SetupMandateRequestData, + }, + router_response_types::{MandateReference, PaymentsResponseData, RefundsResponseData}, + types, +}; +use hyperswitch_interfaces::{ + consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE}, + errors, +}; use masking::{PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use crate::{ - connector::utils::{self, CardData, PaymentsAuthorizeRequestData, RouterData}, - core::errors, - types::{self, domain, storage::enums, transformers::ForeignFrom}, + types::ResponseRouterData, + utils::{self, CardData as _, PaymentsAuthorizeRequestData, RouterData as _}, }; type Error = error_stack::Report; @@ -39,14 +52,14 @@ pub fn get_payment_body( let transaction_data = get_transaction_body(req)?; let body = format!( r#" - + ]]> @@ -92,7 +105,7 @@ fn get_transaction_body( fn get_card_data(req: &types::PaymentsAuthorizeRouterData) -> Result { let card_data = match &req.request.payment_method_data { - domain::PaymentMethodData::Card(card) => { + PaymentMethodData::Card(card) => { let card_holder_name = req.get_billing_full_name()?; if req.request.setup_future_usage == Some(enums::FutureUsage::OffSession) { @@ -132,7 +145,7 @@ fn get_card_data(req: &types::PaymentsAuthorizeRouterData) -> Result { + PaymentMethodData::MandatePayment => { format!( r#" @@ -166,11 +179,11 @@ pub struct BamboraapacAuthType { account_number: Secret, } -impl TryFrom<&types::ConnectorAuthType> for BamboraapacAuthType { +impl TryFrom<&ConnectorAuthType> for BamboraapacAuthType { type Error = error_stack::Report; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { + fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - types::ConnectorAuthType::SignatureKey { + ConnectorAuthType::SignatureKey { api_key, key1, api_secret, @@ -236,21 +249,21 @@ fn get_attempt_status( impl TryFrom< - types::ResponseRouterData< + ResponseRouterData< F, BamboraapacPaymentsResponse, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, + PaymentsAuthorizeData, + PaymentsResponseData, >, - > for types::RouterData + > for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< + item: ResponseRouterData< F, BamboraapacPaymentsResponse, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, + PaymentsAuthorizeData, + PaymentsResponseData, >, ) -> Result { let response_code = item @@ -277,10 +290,11 @@ impl .submit_single_payment_result .response .credit_card_token; - Some(types::MandateReference { + Some(MandateReference { connector_mandate_id, payment_method_id: None, mandate_metadata: None, + connector_mandate_request_reference_id: None, }) } else { None @@ -289,12 +303,12 @@ impl if response_code == 0 { Ok(Self { status: get_attempt_status(response_code, item.data.request.capture_method), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( connector_transaction_id.to_owned(), ), - redirection_data: None, - mandate_reference, + redirection_data: Box::new(None), + mandate_reference: Box::new(mandate_reference), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(connector_transaction_id), @@ -313,7 +327,7 @@ impl .submit_single_payment_result .response .declined_code - .unwrap_or(consts::NO_ERROR_CODE.to_string()); + .unwrap_or(NO_ERROR_CODE.to_string()); let declined_message = item .response @@ -322,10 +336,10 @@ impl .submit_single_payment_result .response .declined_message - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()); + .unwrap_or(NO_ERROR_MESSAGE.to_string()); Ok(Self { status: get_attempt_status(response_code, item.data.request.capture_method), - response: Err(types::ErrorResponse { + response: Err(ErrorResponse { status_code: item.http_code, code, message: declined_message.to_owned(), @@ -343,7 +357,7 @@ pub fn get_setup_mandate_body(req: &types::SetupMandateRouterData) -> Result { + PaymentMethodData::Card(card) => { format!( r#" Result - {} + {} {} {} {} 2 - {} + {} {} ]]> @@ -420,21 +434,21 @@ pub struct MandateResponseBody { impl TryFrom< - types::ResponseRouterData< + ResponseRouterData< F, BamboraapacMandateResponse, - types::SetupMandateRequestData, - types::PaymentsResponseData, + SetupMandateRequestData, + PaymentsResponseData, >, - > for types::RouterData + > for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< + item: ResponseRouterData< F, BamboraapacMandateResponse, - types::SetupMandateRequestData, - types::PaymentsResponseData, + SetupMandateRequestData, + PaymentsResponseData, >, ) -> Result { let response_code = item @@ -458,14 +472,15 @@ impl if response_code == 0 { Ok(Self { status: enums::AttemptStatus::Charged, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::NoResponseId, - redirection_data: None, - mandate_reference: Some(types::MandateReference { + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::NoResponseId, + redirection_data: Box::new(None), + mandate_reference: Box::new(Some(MandateReference { connector_mandate_id: Some(connector_mandate_id), payment_method_id: None, mandate_metadata: None, - }), + connector_mandate_request_reference_id: None, + })), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -479,10 +494,10 @@ impl else { Ok(Self { status: enums::AttemptStatus::Failure, - response: Err(types::ErrorResponse { + response: Err(ErrorResponse { status_code: item.http_code, - code: consts::NO_ERROR_CODE.to_string(), - message: consts::NO_ERROR_MESSAGE.to_string(), + code: NO_ERROR_CODE.to_string(), + message: NO_ERROR_MESSAGE.to_string(), reason: None, attempt_status: None, connector_transaction_id: None, @@ -501,7 +516,7 @@ pub fn get_capture_body( let auth_details = BamboraapacAuthType::try_from(&req.router_data.connector_auth_type)?; let body = format!( r#" - @@ -514,8 +529,8 @@ pub fn get_capture_body( {} {} - - ]]> + + ]]> @@ -566,21 +581,21 @@ pub struct CaptureResponse { impl TryFrom< - types::ResponseRouterData< + ResponseRouterData< F, BamboraapacCaptureResponse, - types::PaymentsCaptureData, - types::PaymentsResponseData, + PaymentsCaptureData, + PaymentsResponseData, >, - > for types::RouterData + > for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< + item: ResponseRouterData< F, BamboraapacCaptureResponse, - types::PaymentsCaptureData, - types::PaymentsResponseData, + PaymentsCaptureData, + PaymentsResponseData, >, ) -> Result { let response_code = item @@ -606,12 +621,12 @@ impl if response_code == 0 { Ok(Self { status: enums::AttemptStatus::Charged, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( connector_transaction_id.to_owned(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata, network_txn_id: None, connector_response_reference_id: Some(connector_transaction_id), @@ -630,7 +645,7 @@ impl .submit_single_capture_result .response .declined_code - .unwrap_or(consts::NO_ERROR_CODE.to_string()); + .unwrap_or(NO_ERROR_CODE.to_string()); let declined_message = item .response .body @@ -638,10 +653,10 @@ impl .submit_single_capture_result .response .declined_message - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()); + .unwrap_or(NO_ERROR_MESSAGE.to_string()); Ok(Self { status: enums::AttemptStatus::Failure, - response: Err(types::ErrorResponse { + response: Err(ErrorResponse { status_code: item.http_code, code, message: declined_message.to_owned(), @@ -663,7 +678,7 @@ pub fn get_refund_body( let auth_details = BamboraapacAuthType::try_from(&req.router_data.connector_auth_type)?; let body = format!( r#" - @@ -677,9 +692,9 @@ pub fn get_refund_body( {} {} - + - ]]> + ]]> @@ -729,34 +744,20 @@ pub struct RefundResponse { declined_message: Option, } -impl ForeignFrom for enums::RefundStatus { - fn foreign_from(item: u8) -> Self { - match item { - 0 => Self::Success, - 1 => Self::Failure, - _ => Self::Pending, - } +fn get_status(item: u8) -> enums::RefundStatus { + match item { + 0 => enums::RefundStatus::Success, + 1 => enums::RefundStatus::Failure, + _ => enums::RefundStatus::Pending, } } -impl - TryFrom< - types::ResponseRouterData< - F, - BamboraapacRefundsResponse, - types::RefundsData, - types::RefundsResponseData, - >, - > for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< - F, - BamboraapacRefundsResponse, - types::RefundsData, - types::RefundsResponseData, - >, + item: ResponseRouterData, ) -> Result { let response_code = item .response @@ -774,9 +775,9 @@ impl .receipt; Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: connector_refund_id.to_owned(), - refund_status: enums::RefundStatus::foreign_from(response_code), + refund_status: get_status(response_code), }), ..item.data }) @@ -792,7 +793,7 @@ pub fn get_payment_sync_body(req: &types::PaymentsSyncRouterData) -> Result @@ -810,8 +811,8 @@ pub fn get_payment_sync_body(req: &types::PaymentsSyncRouterData) -> Result{} {} - - ]]> + + ]]> @@ -867,22 +868,16 @@ pub struct SyncResponse { } impl - TryFrom< - types::ResponseRouterData< - F, - BamboraapacSyncResponse, - types::PaymentsSyncData, - types::PaymentsResponseData, - >, - > for types::RouterData + TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< + item: ResponseRouterData< F, BamboraapacSyncResponse, - types::PaymentsSyncData, - types::PaymentsResponseData, + PaymentsSyncData, + PaymentsResponseData, >, ) -> Result { let response_code = item @@ -905,12 +900,12 @@ impl if response_code == 0 { Ok(Self { status: get_attempt_status(response_code, item.data.request.capture_method), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( connector_transaction_id.to_owned(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(connector_transaction_id), @@ -930,7 +925,7 @@ impl .query_response .response .declined_code - .unwrap_or(consts::NO_ERROR_CODE.to_string()); + .unwrap_or(NO_ERROR_CODE.to_string()); let declined_message = item .response .body @@ -939,10 +934,10 @@ impl .query_response .response .declined_message - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()); + .unwrap_or(NO_ERROR_MESSAGE.to_string()); Ok(Self { status: get_attempt_status(response_code, item.data.request.capture_method), - response: Err(types::ErrorResponse { + response: Err(ErrorResponse { status_code: item.http_code, code, message: declined_message.to_owned(), @@ -961,7 +956,7 @@ pub fn get_refund_sync_body(req: &types::RefundSyncRouterData) -> Result let body = format!( r#" - @@ -979,8 +974,8 @@ pub fn get_refund_sync_body(req: &types::RefundSyncRouterData) -> Result {} {} - - ]]> + + ]]> @@ -995,24 +990,12 @@ pub fn get_refund_sync_body(req: &types::RefundSyncRouterData) -> Result Ok(body.as_bytes().to_vec()) } -impl - TryFrom< - types::ResponseRouterData< - F, - BamboraapacSyncResponse, - types::RefundsData, - types::RefundsResponseData, - >, - > for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< - F, - BamboraapacSyncResponse, - types::RefundsData, - types::RefundsResponseData, - >, + item: ResponseRouterData, ) -> Result { let response_code = item .response @@ -1031,9 +1014,9 @@ impl .response .receipt; Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: connector_refund_id.to_owned(), - refund_status: enums::RefundStatus::foreign_from(response_code), + refund_status: get_status(response_code), }), ..item.data }) diff --git a/crates/router/src/connector/billwerk.rs b/crates/hyperswitch_connectors/src/connectors/billwerk.rs similarity index 65% rename from crates/router/src/connector/billwerk.rs rename to crates/hyperswitch_connectors/src/connectors/billwerk.rs index 97106d511b88..3987039de974 100644 --- a/crates/router/src/connector/billwerk.rs +++ b/crates/hyperswitch_connectors/src/connectors/billwerk.rs @@ -1,32 +1,58 @@ pub mod transformers; +use api_models::webhooks::{IncomingWebhookEvent, ObjectReferenceId}; use base64::Engine; -use common_utils::types::{AmountConvertor, MinorUnit, MinorUnitForConnector}; -use error_stack::{report, ResultExt}; -use masking::PeekInterface; -use transformers as billwerk; - -use super::utils::{ - RefundsRequestData, {self as connector_utils}, +use common_utils::{ + consts::BASE64_ENGINE, + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, MinorUnit, MinorUnitForConnector}, }; -use crate::{ - configs::settings, - consts, - core::errors::{self, CustomResult}, - events::connector_api_logs::ConnectorEvent, - headers, - services::{ - self, - request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, +use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::{ + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, types::{ - self, - api::{self, ConnectorCommon, ConnectorCommonExt}, - ErrorResponse, RequestContent, Response, + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, TokenizationRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, }, - utils::BytesExt, + configs::Connectors, + consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE}, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, }; +use masking::{Mask, PeekInterface}; +use transformers::{ + self as billwerk, BillwerkAuthType, BillwerkCaptureRequest, BillwerkErrorResponse, + BillwerkPaymentsRequest, BillwerkPaymentsResponse, BillwerkRefundRequest, BillwerkRouterData, + BillwerkTokenRequest, BillwerkTokenResponse, +}; + +use crate::{ + constants::headers, + types::ResponseRouterData, + utils::{construct_not_implemented_error_report, convert_amount, RefundsRequestData}, +}; + #[derive(Clone)] pub struct Billwerk { amount_converter: &'static (dyn AmountConvertor + Sync), @@ -39,7 +65,6 @@ impl Billwerk { } } } - impl api::Payment for Billwerk {} impl api::PaymentSession for Billwerk {} impl api::ConnectorAccessToken for Billwerk {} @@ -59,9 +84,9 @@ where { fn build_headers( &self, - req: &types::RouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let mut header = vec![( headers::CONTENT_TYPE.to_string(), self.get_content_type().to_string().into(), @@ -85,17 +110,17 @@ impl ConnectorCommon for Billwerk { "application/json" } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.billwerk.base_url.as_ref() } fn get_auth_header( &self, - auth_type: &types::ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { - let auth = billwerk::BillwerkAuthType::try_from(auth_type) + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + let auth = BillwerkAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; - let encoded_api_key = consts::BASE64_ENGINE.encode(format!("{}:", auth.api_key.peek())); + let encoded_api_key = BASE64_ENGINE.encode(format!("{}:", auth.api_key.peek())); Ok(vec![( headers::AUTHORIZATION.to_string(), format!("Basic {encoded_api_key}").into_masked(), @@ -107,7 +132,7 @@ impl ConnectorCommon for Billwerk { res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { - let response: billwerk::BillwerkErrorResponse = res + let response: BillwerkErrorResponse = res .response .parse_struct("BillwerkErrorResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; @@ -119,10 +144,8 @@ impl ConnectorCommon for Billwerk { status_code: res.status_code, code: response .code - .map_or(consts::NO_ERROR_CODE.to_string(), |code| code.to_string()), - message: response - .message - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + .map_or(NO_ERROR_CODE.to_string(), |code| code.to_string()), + message: response.message.unwrap_or(NO_ERROR_MESSAGE.to_string()), reason: Some(response.error), attempt_status: None, connector_transaction_id: None, @@ -131,54 +154,44 @@ impl ConnectorCommon for Billwerk { } impl ConnectorValidation for Billwerk { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: common_enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - common_enums::CaptureMethod::Automatic | common_enums::CaptureMethod::Manual => Ok(()), + common_enums::CaptureMethod::Automatic + | common_enums::CaptureMethod::Manual + | common_enums::CaptureMethod::SequentialAutomatic => Ok(()), common_enums::CaptureMethod::ManualMultiple | common_enums::CaptureMethod::Scheduled => Err( - super::utils::construct_not_implemented_error_report(capture_method, self.id()), + construct_not_implemented_error_report(capture_method, self.id()), ), } } } -impl ConnectorIntegration - for Billwerk -{ +impl ConnectorIntegration for Billwerk { //TODO: implement sessions flow } -impl ConnectorIntegration - for Billwerk -{ -} +impl ConnectorIntegration for Billwerk {} -impl - ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Billwerk +impl ConnectorIntegration + for Billwerk { } -impl - ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Billwerk +impl ConnectorIntegration + for Billwerk { fn get_headers( &self, - req: &types::TokenizationRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &TokenizationRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -188,8 +201,8 @@ impl fn get_url( &self, - _req: &types::TokenizationRouterData, - connectors: &settings::Connectors, + _req: &TokenizationRouterData, + connectors: &Connectors, ) -> CustomResult { let base_url = connectors .billwerk @@ -201,21 +214,21 @@ impl fn get_request_body( &self, - req: &types::TokenizationRouterData, - _connectors: &settings::Connectors, + req: &TokenizationRouterData, + _connectors: &Connectors, ) -> CustomResult { - let connector_req = billwerk::BillwerkTokenRequest::try_from(req)?; + let connector_req = BillwerkTokenRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, - req: &types::TokenizationRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &TokenizationRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::TokenizationType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::TokenizationType::get_headers(self, req, connectors)?) @@ -228,20 +241,20 @@ impl fn handle_response( &self, - data: &types::TokenizationRouterData, + data: &TokenizationRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult + ) -> CustomResult where - types::PaymentsResponseData: Clone, + PaymentsResponseData: Clone, { - let response: billwerk::BillwerkTokenResponse = res + let response: BillwerkTokenResponse = res .response .parse_struct("BillwerkTokenResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -266,14 +279,12 @@ impl } } -impl ConnectorIntegration - for Billwerk -{ +impl ConnectorIntegration for Billwerk { fn get_headers( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -283,36 +294,36 @@ impl ConnectorIntegration CustomResult { Ok(format!("{}v1/charge", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::PaymentsAuthorizeRouterData, - _connectors: &settings::Connectors, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, ) -> CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_amount, req.request.currency, )?; - let connector_router_data = billwerk::BillwerkRouterData::try_from((amount, req))?; - let connector_req = billwerk::BillwerkPaymentsRequest::try_from(&connector_router_data)?; + let connector_router_data = BillwerkRouterData::try_from((amount, req))?; + let connector_req = BillwerkPaymentsRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsAuthorizeType::get_url( self, req, connectors, )?) @@ -329,17 +340,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { - let response: billwerk::BillwerkPaymentsResponse = res + ) -> CustomResult { + let response: BillwerkPaymentsResponse = res .response .parse_struct("Billwerk BillwerkPaymentsResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -363,14 +374,12 @@ impl ConnectorIntegration - for Billwerk -{ +impl ConnectorIntegration for Billwerk { fn get_headers( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -380,8 +389,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}v1/charge/{}", @@ -392,12 +401,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) + RequestBuilder::new() + .method(Method::Get) .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) @@ -407,17 +416,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { - let response: billwerk::BillwerkPaymentsResponse = res + ) -> CustomResult { + let response: BillwerkPaymentsResponse = res .response .parse_struct("billwerk BillwerkPaymentsResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -441,14 +450,12 @@ impl ConnectorIntegration - for Billwerk -{ +impl ConnectorIntegration for Billwerk { fn get_headers( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -458,8 +465,8 @@ impl ConnectorIntegration CustomResult { let connector_transaction_id = &req.request.connector_transaction_id; Ok(format!( @@ -470,28 +477,28 @@ impl ConnectorIntegration CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_amount_to_capture, req.request.currency, )?; - let connector_router_data = billwerk::BillwerkRouterData::try_from((amount, req))?; - let connector_req = billwerk::BillwerkCaptureRequest::try_from(&connector_router_data)?; + let connector_router_data = BillwerkRouterData::try_from((amount, req))?; + let connector_req = BillwerkCaptureRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsCaptureType::get_headers( @@ -506,17 +513,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { - let response: billwerk::BillwerkPaymentsResponse = res + ) -> CustomResult { + let response: BillwerkPaymentsResponse = res .response .parse_struct("Billwerk BillwerkPaymentsResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -540,14 +547,12 @@ impl ConnectorIntegration - for Billwerk -{ +impl ConnectorIntegration for Billwerk { fn get_headers( &self, - req: &types::PaymentsCancelRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -557,8 +562,8 @@ impl ConnectorIntegration CustomResult { let connector_transaction_id = &req.request.connector_transaction_id; Ok(format!( @@ -569,12 +574,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) @@ -584,17 +589,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { - let response: billwerk::BillwerkPaymentsResponse = res + ) -> CustomResult { + let response: BillwerkPaymentsResponse = res .response .parse_struct("Billwerk BillwerkPaymentsResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -618,14 +623,12 @@ impl ConnectorIntegration - for Billwerk -{ +impl ConnectorIntegration for Billwerk { fn get_headers( &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -635,35 +638,35 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, + _req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!("{}v1/refund", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::RefundsRouterData, - _connectors: &settings::Connectors, + req: &RefundsRouterData, + _connectors: &Connectors, ) -> CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_refund_amount, req.request.currency, )?; - let connector_router_data = billwerk::BillwerkRouterData::try_from((amount, req))?; - let connector_req = billwerk::BillwerkRefundRequest::try_from(&connector_router_data)?; + let connector_router_data = BillwerkRouterData::try_from((amount, req))?; + let connector_req = BillwerkRefundRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) .url(&types::RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundExecuteType::get_headers( @@ -678,17 +681,17 @@ impl ConnectorIntegration, + data: &RefundsRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult, errors::ConnectorError> { let response: billwerk::RefundResponse = res .response .parse_struct("billwerk RefundResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -712,12 +715,12 @@ impl ConnectorIntegration for Billwerk { +impl ConnectorIntegration for Billwerk { fn get_headers( &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -727,8 +730,8 @@ impl ConnectorIntegration CustomResult { let refund_id = req.request.get_connector_refund_id()?; Ok(format!( @@ -739,12 +742,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) + RequestBuilder::new() + .method(Method::Get) .url(&types::RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundSyncType::get_headers(self, req, connectors)?) @@ -757,17 +760,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: billwerk::RefundResponse = res .response .parse_struct("billwerk RefundSyncResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -792,25 +795,27 @@ impl ConnectorIntegration, - ) -> CustomResult { + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_event_type( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_resource_object( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for Billwerk {} diff --git a/crates/router/src/connector/billwerk/transformers.rs b/crates/hyperswitch_connectors/src/connectors/billwerk/transformers.rs similarity index 75% rename from crates/router/src/connector/billwerk/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/billwerk/transformers.rs index c6f2be9dbfc3..05b9abae0515 100644 --- a/crates/router/src/connector/billwerk/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/billwerk/transformers.rs @@ -1,16 +1,30 @@ +use common_enums::enums; use common_utils::{ id_type, pii::{Email, SecretSerdeValue}, types::MinorUnit, }; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{ConnectorAuthType, ErrorResponse, PaymentMethodToken, RouterData}, + router_flow_types::{ + payments, + refunds::{Execute, RSync}, + }, + router_request_types::ResponseId, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types, +}; +use hyperswitch_interfaces::{ + consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE}, + errors, +}; use masking::{ExposeInterface, Secret}; use serde::{Deserialize, Serialize}; use crate::{ - connector::utils::{self, CardData, PaymentsAuthorizeRequestData, RouterData}, - consts, - core::errors, - types::{self, api, domain, storage::enums}, + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::{self, CardData as _, PaymentsAuthorizeRequestData, RouterData as _}, }; pub struct BillwerkRouterData { @@ -33,11 +47,11 @@ pub struct BillwerkAuthType { pub(super) public_api_key: Secret, } -impl TryFrom<&types::ConnectorAuthType> for BillwerkAuthType { +impl TryFrom<&ConnectorAuthType> for BillwerkAuthType { type Error = error_stack::Report; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { + fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - types::ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { + ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { api_key: api_key.to_owned(), public_api_key: key1.to_owned(), }), @@ -75,7 +89,7 @@ impl TryFrom<&types::TokenizationRouterData> for BillwerkTokenRequest { type Error = error_stack::Report; fn try_from(item: &types::TokenizationRouterData) -> Result { match item.request.payment_method_data.clone() { - domain::PaymentMethodData::Card(ccard) => { + PaymentMethodData::Card(ccard) => { let connector_auth = &item.connector_auth_type; let auth_type = BillwerkAuthType::try_from(connector_auth)?; Ok(Self { @@ -89,22 +103,24 @@ impl TryFrom<&types::TokenizationRouterData> for BillwerkTokenRequest { strong_authentication_rule: None, }) } - domain::payments::PaymentMethodData::Wallet(_) - | domain::payments::PaymentMethodData::CardRedirect(_) - | domain::payments::PaymentMethodData::PayLater(_) - | domain::payments::PaymentMethodData::BankRedirect(_) - | domain::payments::PaymentMethodData::BankDebit(_) - | domain::payments::PaymentMethodData::BankTransfer(_) - | domain::payments::PaymentMethodData::Crypto(_) - | domain::payments::PaymentMethodData::MandatePayment - | domain::payments::PaymentMethodData::Reward - | domain::payments::PaymentMethodData::RealTimePayment(_) - | domain::payments::PaymentMethodData::Upi(_) - | domain::payments::PaymentMethodData::Voucher(_) - | domain::payments::PaymentMethodData::GiftCard(_) - | domain::payments::PaymentMethodData::OpenBanking(_) - | domain::payments::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + PaymentMethodData::Wallet(_) + | PaymentMethodData::CardRedirect(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("billwerk"), ) @@ -122,25 +138,25 @@ pub struct BillwerkTokenResponse { impl TryFrom< - types::ResponseRouterData< - api::PaymentMethodToken, + ResponseRouterData< + payments::PaymentMethodToken, BillwerkTokenResponse, T, - types::PaymentsResponseData, + PaymentsResponseData, >, - > for types::RouterData + > for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< - api::PaymentMethodToken, + item: ResponseRouterData< + payments::PaymentMethodToken, BillwerkTokenResponse, T, - types::PaymentsResponseData, + PaymentsResponseData, >, ) -> Result { Ok(Self { - response: Ok(types::PaymentsResponseData::TokenizationResponse { + response: Ok(PaymentsResponseData::TokenizationResponse { token: item.response.id.expose(), }), ..item.data @@ -183,7 +199,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(pm_token), + PaymentMethodToken::Token(pm_token) => Ok(pm_token), _ => Err(errors::ConnectorError::MissingRequiredField { field_name: "payment_method_token", }), @@ -240,31 +256,25 @@ pub struct BillwerkPaymentsResponse { error_state: Option, } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< - F, - BillwerkPaymentsResponse, - T, - types::PaymentsResponseData, - >, + item: ResponseRouterData, ) -> Result { let error_response = if item.response.error.is_some() || item.response.error_state.is_some() { - Some(types::ErrorResponse { + Some(ErrorResponse { code: item .response .error_state .clone() - .unwrap_or(consts::NO_ERROR_CODE.to_string()), + .unwrap_or(NO_ERROR_CODE.to_string()), message: item .response .error_state - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + .unwrap_or(NO_ERROR_MESSAGE.to_string()), reason: item.response.error, status_code: item.http_code, attempt_status: None, @@ -273,10 +283,10 @@ impl } else { None }; - let payments_response = types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(item.response.handle.clone()), - redirection_data: None, - mandate_reference: None, + let payments_response = PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.handle.clone()), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(item.response.handle), @@ -353,15 +363,15 @@ pub struct RefundResponse { state: RefundState, } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item.response.id.to_string(), refund_status: enums::RefundStatus::from(item.response.state), }), @@ -370,15 +380,13 @@ impl TryFrom> } } -impl TryFrom> - for types::RefundsRouterData -{ +impl TryFrom> for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item.response.id.to_string(), refund_status: enums::RefundStatus::from(item.response.state), }), diff --git a/crates/hyperswitch_connectors/src/connectors/bitpay.rs b/crates/hyperswitch_connectors/src/connectors/bitpay.rs index 032b8cf051fa..c2a2c7b76a17 100644 --- a/crates/hyperswitch_connectors/src/connectors/bitpay.rs +++ b/crates/hyperswitch_connectors/src/connectors/bitpay.rs @@ -27,7 +27,10 @@ use hyperswitch_domain_models::{ }, }; use hyperswitch_interfaces::{ - api::{self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorValidation}, + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, configs::Connectors, consts, errors, events::connector_api_logs::ConnectorEvent, @@ -412,3 +415,5 @@ impl webhooks::IncomingWebhook for Bitpay { Ok(Box::new(notif)) } } + +impl ConnectorSpecifications for Bitpay {} diff --git a/crates/hyperswitch_connectors/src/connectors/bitpay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/bitpay/transformers.rs index 1c42bfd08c83..4071b58a880b 100644 --- a/crates/hyperswitch_connectors/src/connectors/bitpay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/bitpay/transformers.rs @@ -159,8 +159,8 @@ impl TryFrom Result> { let price = item.amount; let currency = item.router_data.request.currency.to_string(); - let redirect_url = item.router_data.request.get_return_url()?; + let redirect_url = item.router_data.request.get_router_return_url()?; let notification_url = item.router_data.request.get_webhook_url()?; let transaction_speed = TransactionSpeed::Medium; let auth_type = item.router_data.connector_auth_type.clone(); diff --git a/crates/router/src/connector/bluesnap.rs b/crates/hyperswitch_connectors/src/connectors/bluesnap.rs similarity index 77% rename from crates/router/src/connector/bluesnap.rs rename to crates/hyperswitch_connectors/src/connectors/bluesnap.rs index e6f01700c6d7..8fac36424e23 100644 --- a/crates/router/src/connector/bluesnap.rs +++ b/crates/hyperswitch_connectors/src/connectors/bluesnap.rs @@ -1,46 +1,70 @@ pub mod transformers; +use api_models::webhooks::IncomingWebhookEvent; use base64::Engine; +use common_enums::{enums, CallConnectorAction, PaymentAction}; use common_utils::{ + consts::BASE64_ENGINE, crypto, - ext_traits::{StringExt, ValueExt}, - request::RequestContent, + errors::CustomResult, + ext_traits::{BytesExt, StringExt, ValueExt}, + request::{Method, Request, RequestBuilder, RequestContent}, types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector}, }; -use diesel_models::enums; use error_stack::{report, ResultExt}; -use masking::PeekInterface; -use transformers as bluesnap; - -use super::utils::{ - self as connector_utils, get_error_code_error_message_based_on_priority, ConnectorErrorType, - ConnectorErrorTypeMapping, PaymentsAuthorizeRequestData, RefundsRequestData, RouterData, -}; -use crate::{ - configs::settings, - consts, - core::{ - errors::{self, CustomResult}, - payments, +use hyperswitch_domain_models::{ + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + CompleteAuthorize, }, - events::connector_api_logs::ConnectorEvent, - headers, logger, - services::{ - self, - request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, + router_request_types::{ + AccessTokenRequestData, CompleteAuthorizeData, PaymentMethodTokenizationData, + PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, + PaymentsSyncData, RefundsData, ResponseId, SetupMandateRequestData, }, + router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData}, types::{ - self, - api::{self, ConnectorCommon, ConnectorCommonExt}, - transformers::ForeignTryFrom, - ErrorResponse, Response, + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsCompleteAuthorizeRouterData, PaymentsSessionRouterData, PaymentsSyncRouterData, + RefundSyncRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorRedirectResponse, + ConnectorSpecifications, ConnectorValidation, + }, + configs::Connectors, + consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE}, + disputes::DisputePayload, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, +}; +use masking::{Mask, PeekInterface}; +use router_env::logger; +use transformers as bluesnap; + +use crate::{ + constants::headers, + types::ResponseRouterData, + utils::{ + construct_not_supported_error_report, convert_amount, + get_error_code_error_message_based_on_priority, get_header_key_value, get_http_header, + to_connector_meta_from_secret, to_currency_lower_unit, ConnectorErrorType, + ConnectorErrorTypeMapping, ForeignTryFrom, PaymentsAuthorizeRequestData, + RefundsRequestData, RouterData as _, }, - utils::BytesExt, }; pub const BLUESNAP_TRANSACTION_NOT_FOUND: &str = "is not authorized to view merchant-transaction:"; +pub const REQUEST_TIMEOUT_PAYMENT_NOT_FOUND: &str = "Timed out ,payment not found"; + #[derive(Clone)] pub struct Bluesnap { amount_converter: &'static (dyn AmountConvertor + Sync), @@ -60,9 +84,9 @@ where { fn build_headers( &self, - req: &types::RouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let mut header = self.get_auth_header(&req.connector_auth_type)?; header.push(( headers::CONTENT_TYPE.to_string(), @@ -84,18 +108,18 @@ impl ConnectorCommon for Bluesnap { fn common_get_content_type(&self) -> &'static str { "application/json" } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.bluesnap.base_url.as_ref() } fn get_auth_header( &self, - auth_type: &types::ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { let auth = bluesnap::BluesnapAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; let encoded_api_key = - consts::BASE64_ENGINE.encode(format!("{}:{}", auth.key1.peek(), auth.api_key.peek())); + BASE64_ENGINE.encode(format!("{}:{}", auth.key1.peek(), auth.api_key.peek())); Ok(vec![( headers::AUTHORIZATION.to_string(), format!("Basic {encoded_api_key}").into_masked(), @@ -134,10 +158,10 @@ impl ConnectorCommon for Bluesnap { code: option_error_code_message .clone() .map(|error_code_message| error_code_message.error_code) - .unwrap_or(consts::NO_ERROR_CODE.to_string()), + .unwrap_or(NO_ERROR_CODE.to_string()), message: option_error_code_message .map(|error_code_message| error_code_message.error_message) - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + .unwrap_or(NO_ERROR_MESSAGE.to_string()), reason: Some(reason), attempt_status: None, connector_transaction_id: None, @@ -158,7 +182,7 @@ impl ConnectorCommon for Bluesnap { ( format!( "{} in bluesnap dashboard", - consts::REQUEST_TIMEOUT_PAYMENT_NOT_FOUND + REQUEST_TIMEOUT_PAYMENT_NOT_FOUND ), Some(enums::AttemptStatus::Failure), // when bluesnap throws 403 for payment not found, we update the payment status to failure. ) @@ -167,7 +191,7 @@ impl ConnectorCommon for Bluesnap { }; ErrorResponse { status_code: res.status_code, - code: consts::NO_ERROR_CODE.to_string(), + code: NO_ERROR_CODE.to_string(), message: error_response, reason: Some(error_res), attempt_status, @@ -180,23 +204,26 @@ impl ConnectorCommon for Bluesnap { } impl ConnectorValidation for Bluesnap { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( - connector_utils::construct_not_supported_error_report(capture_method, self.id()), + construct_not_supported_error_report(capture_method, self.id()), ), } } fn validate_psync_reference_id( &self, - data: &hyperswitch_domain_models::router_request_types::PaymentsSyncData, + data: &PaymentsSyncData, is_three_ds: bool, status: enums::AttemptStatus, connector_meta_data: Option, @@ -220,7 +247,7 @@ impl ConnectorValidation for Bluesnap { } // if merchant_id is present, psync can be made along with attempt_id let meta_data: CustomResult = - connector_utils::to_connector_meta_from_secret(connector_meta_data.clone()); + to_connector_meta_from_secret(connector_meta_data.clone()); meta_data.map(|_| ()) } @@ -230,33 +257,21 @@ impl api::Payment for Bluesnap {} impl api::PaymentToken for Bluesnap {} -impl - ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Bluesnap +impl ConnectorIntegration + for Bluesnap { // Not Implemented (R) } impl api::MandateSetup for Bluesnap {} -impl - ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Bluesnap +impl ConnectorIntegration + for Bluesnap { fn build_request( &self, - _req: &types::RouterData< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - >, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err( errors::ConnectorError::NotImplemented("Setup Mandate flow for Bluesnap".to_string()) .into(), @@ -266,14 +281,12 @@ impl impl api::PaymentVoid for Bluesnap {} -impl ConnectorIntegration - for Bluesnap -{ +impl ConnectorIntegration for Bluesnap { fn get_headers( &self, - req: &types::PaymentsCancelRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -283,8 +296,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}{}", @@ -295,8 +308,8 @@ impl ConnectorIntegration CustomResult { let connector_req = bluesnap::BluesnapVoidRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -304,11 +317,11 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Put) + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Put) .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) @@ -321,17 +334,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: bluesnap::BluesnapPaymentsResponse = res .response .parse_struct("BluesnapPaymentsResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -350,20 +363,15 @@ impl ConnectorIntegration - for Bluesnap -{ -} +impl ConnectorIntegration for Bluesnap {} impl api::PaymentSync for Bluesnap {} -impl ConnectorIntegration - for Bluesnap -{ +impl ConnectorIntegration for Bluesnap { fn get_headers( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -373,13 +381,13 @@ impl ConnectorIntegration CustomResult { let connector_transaction_id = req.request.connector_transaction_id.clone(); match connector_transaction_id { // if connector_transaction_id is present, we always sync with connector_transaction_id - types::ResponseId::ConnectorTransactionId(trans_id) => { + ResponseId::ConnectorTransactionId(trans_id) => { get_psync_url_with_connector_transaction_id( trans_id, self.base_url(connectors).to_string(), @@ -388,7 +396,7 @@ impl ConnectorIntegration { // if connector_transaction_id is not present, we sync with merchant_transaction_id let meta_data: bluesnap::BluesnapConnectorMetaData = - connector_utils::to_connector_meta_from_secret(req.connector_meta_data.clone()) + to_connector_meta_from_secret(req.connector_meta_data.clone()) .change_context(errors::ConnectorError::ResponseHandlingFailed)?; get_url_with_merchant_transaction_id( self.base_url(connectors).to_string(), @@ -401,12 +409,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) + RequestBuilder::new() + .method(Method::Get) .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) @@ -424,17 +432,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: bluesnap::BluesnapPaymentsResponse = res .response .parse_struct("BluesnapPaymentsResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -444,14 +452,12 @@ impl ConnectorIntegration - for Bluesnap -{ +impl ConnectorIntegration for Bluesnap { fn get_headers( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -461,8 +467,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}{}", @@ -473,10 +479,10 @@ impl ConnectorIntegration CustomResult { - let amount_to_capture = connector_utils::convert_amount( + let amount_to_capture = convert_amount( self.amount_converter, req.request.minor_amount_to_capture, req.request.currency, @@ -489,11 +495,11 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Put) + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Put) .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsCaptureType::get_headers( @@ -508,17 +514,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: bluesnap::BluesnapPaymentsResponse = res .response .parse_struct("Bluesnap BluesnapPaymentsResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -538,14 +544,12 @@ impl ConnectorIntegration - for Bluesnap -{ +impl ConnectorIntegration for Bluesnap { fn get_headers( &self, - req: &types::PaymentsSessionRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsSessionRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -555,8 +559,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}{}", @@ -567,8 +571,8 @@ impl ConnectorIntegration CustomResult { let connector_req = bluesnap::BluesnapCreateWalletToken::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -576,12 +580,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsSessionRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsSessionType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSessionType::get_headers( @@ -596,10 +600,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: bluesnap::BluesnapWalletTokenResponse = res .response .parse_struct("BluesnapWalletTokenResponse") @@ -609,11 +613,10 @@ impl ConnectorIntegration - for Bluesnap -{ +impl ConnectorIntegration for Bluesnap { fn get_headers( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -650,8 +651,8 @@ impl ConnectorIntegration CustomResult { if req.is_three_ds() && req.request.is_card() { Ok(format!( @@ -670,10 +671,10 @@ impl ConnectorIntegration CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_amount, req.request.currency, @@ -695,12 +696,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsAuthorizeType::get_url( self, req, connectors, )?) @@ -717,13 +718,13 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { match (data.is_three_ds() && data.request.is_card(), res.headers) { (true, Some(headers)) => { - let location = connector_utils::get_http_header("Location", &headers) + let location = get_http_header("Location", &headers) .change_context(errors::ConnectorError::ResponseHandlingFailed)?; // If location headers are not present connector will return 4XX so this error will never be propagated let payment_fields_token = location .split('/') @@ -737,14 +738,14 @@ impl ConnectorIntegration for Bluesnap +impl ConnectorIntegration + for Bluesnap { fn get_headers( &self, - req: &types::PaymentsCompleteAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } fn get_content_type(&self) -> &'static str { @@ -800,8 +797,8 @@ impl } fn get_url( &self, - _req: &types::PaymentsCompleteAuthorizeRouterData, - connectors: &settings::Connectors, + _req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!( "{}services/2/transactions", @@ -810,10 +807,10 @@ impl } fn get_request_body( &self, - req: &types::PaymentsCompleteAuthorizeRouterData, - _connectors: &settings::Connectors, + req: &PaymentsCompleteAuthorizeRouterData, + _connectors: &Connectors, ) -> CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_amount, req.request.currency, @@ -825,12 +822,12 @@ impl } fn build_request( &self, - req: &types::PaymentsCompleteAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsCompleteAuthorizeType::get_url( self, req, connectors, )?) @@ -846,17 +843,17 @@ impl } fn handle_response( &self, - data: &types::PaymentsCompleteAuthorizeRouterData, + data: &PaymentsCompleteAuthorizeRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: bluesnap::BluesnapPaymentsResponse = res .response .parse_struct("BluesnapPaymentsResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -877,14 +874,12 @@ impl api::Refund for Bluesnap {} impl api::RefundExecute for Bluesnap {} impl api::RefundSync for Bluesnap {} -impl ConnectorIntegration - for Bluesnap -{ +impl ConnectorIntegration for Bluesnap { fn get_headers( &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -894,8 +889,8 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, + req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!( "{}{}{}", @@ -907,10 +902,10 @@ impl ConnectorIntegration, - _connectors: &settings::Connectors, + req: &RefundsRouterData, + _connectors: &Connectors, ) -> CustomResult { - let refund_amount = connector_utils::convert_amount( + let refund_amount = convert_amount( self.amount_converter, req.request.minor_refund_amount, req.request.currency, @@ -922,11 +917,11 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) .url(&types::RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundExecuteType::get_headers( @@ -941,17 +936,17 @@ impl ConnectorIntegration, + data: &RefundsRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult, errors::ConnectorError> { let response: bluesnap::RefundResponse = res .response .parse_struct("bluesnap RefundResponse") .change_context(errors::ConnectorError::RequestEncodingFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -968,12 +963,12 @@ impl ConnectorIntegration for Bluesnap { +impl ConnectorIntegration for Bluesnap { fn get_headers( &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -983,14 +978,14 @@ impl ConnectorIntegration CustomResult { if req.request.payment_amount == req.request.refund_amount { let meta_data: CustomResult< bluesnap::BluesnapConnectorMetaData, errors::ConnectorError, - > = connector_utils::to_connector_meta_from_secret(req.connector_meta_data.clone()); + > = to_connector_meta_from_secret(req.connector_meta_data.clone()); match meta_data { // if merchant_id is present, rsync can be made using merchant_transaction_id @@ -1012,12 +1007,12 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) + RequestBuilder::new() + .method(Method::Get) .url(&types::RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundSyncType::get_headers(self, req, connectors)?) @@ -1027,17 +1022,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: bluesnap::BluesnapPaymentsResponse = res .response .parse_struct("bluesnap BluesnapPaymentsResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -1055,39 +1050,37 @@ impl ConnectorIntegration, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Ok(Box::new(crypto::HmacSha256)) } fn get_webhook_source_verification_signature( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { - let security_header = - connector_utils::get_header_key_value("bls-signature", request.headers)?; + let security_header = get_header_key_value("bls-signature", request.headers)?; hex::decode(security_header) .change_context(errors::ConnectorError::WebhookSignatureNotFound) } fn get_webhook_source_verification_message( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, _merchant_id: &common_utils::id_type::MerchantId, _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { - let timestamp = - connector_utils::get_header_key_value("bls-ipn-timestamp", request.headers)?; + let timestamp = get_header_key_value("bls-ipn-timestamp", request.headers)?; Ok(format!("{}{}", timestamp, String::from_utf8_lossy(request.body)).into_bytes()) } fn get_webhook_object_reference_id( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult { let webhook_body: bluesnap::BluesnapWebhookBody = serde_urlencoded::from_bytes(request.body) @@ -1129,27 +1122,27 @@ impl api::IncomingWebhook for Bluesnap { fn get_webhook_event_type( &self, - request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { let details: bluesnap::BluesnapWebhookObjectEventType = serde_urlencoded::from_bytes(request.body) .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?; - api::IncomingWebhookEvent::try_from(details) + IncomingWebhookEvent::try_from(details) } fn get_dispute_details( &self, - request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { let dispute_details: bluesnap::BluesnapDisputeWebhookBody = serde_urlencoded::from_bytes(request.body) .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; - Ok(api::disputes::DisputePayload { - amount: connector_utils::to_currency_lower_unit( + Ok(DisputePayload { + amount: to_currency_lower_unit( dispute_details.invoice_charge_amount.abs().to_string(), dispute_details.currency, )?, - currency: dispute_details.currency.to_string(), + currency: dispute_details.currency, dispute_stage: api_models::enums::DisputeStage::Dispute, connector_dispute_id: dispute_details.reversal_ref_num, connector_reason: dispute_details.reversal_reason, @@ -1163,7 +1156,7 @@ impl api::IncomingWebhook for Bluesnap { fn get_webhook_resource_object( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { let resource: bluesnap::BluesnapWebhookObjectResource = serde_urlencoded::from_bytes(request.body) @@ -1173,19 +1166,18 @@ impl api::IncomingWebhook for Bluesnap { } } -impl services::ConnectorRedirectResponse for Bluesnap { +impl ConnectorRedirectResponse for Bluesnap { fn get_flow_type( &self, _query_params: &str, json_payload: Option, - action: services::PaymentAction, - ) -> CustomResult { + action: PaymentAction, + ) -> CustomResult { match action { - services::PaymentAction::PSync - | services::PaymentAction::PaymentAuthenticateCompleteAuthorize => { - Ok(payments::CallConnectorAction::Trigger) + PaymentAction::PSync | PaymentAction::PaymentAuthenticateCompleteAuthorize => { + Ok(CallConnectorAction::Trigger) } - services::PaymentAction::CompleteAuthorize => { + PaymentAction::CompleteAuthorize => { let redirection_response: bluesnap::BluesnapRedirectionResponse = json_payload .ok_or(errors::ConnectorError::MissingConnectorRedirectionPayload { field_name: "json_payload", @@ -1199,8 +1191,8 @@ impl services::ConnectorRedirectResponse for Bluesnap { .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; match redirection_result.status.as_str() { - "Success" => Ok(payments::CallConnectorAction::Trigger), - _ => Ok(payments::CallConnectorAction::StatusUpdate { + "Success" => Ok(CallConnectorAction::Trigger), + _ => Ok(CallConnectorAction::StatusUpdate { status: enums::AttemptStatus::AuthenticationFailed, error_code: redirection_result.code, error_message: redirection_result @@ -1368,7 +1360,7 @@ fn get_psync_url_with_connector_transaction_id( } fn get_rsync_url_with_connector_refund_id( - req: &types::RefundSyncRouterData, + req: &RefundSyncRouterData, base_url: String, ) -> CustomResult { Ok(format!( @@ -1378,3 +1370,5 @@ fn get_rsync_url_with_connector_refund_id( req.request.get_connector_refund_id()? )) } + +impl ConnectorSpecifications for Bluesnap {} diff --git a/crates/router/src/connector/bluesnap/transformers.rs b/crates/hyperswitch_connectors/src/connectors/bluesnap/transformers.rs similarity index 76% rename from crates/router/src/connector/bluesnap/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/bluesnap/transformers.rs index 6ebcdf0e765b..7970b1b1d42e 100644 --- a/crates/router/src/connector/bluesnap/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/bluesnap/transformers.rs @@ -1,32 +1,44 @@ use std::collections::HashMap; -use api_models::{enums as api_enums, payments}; +use api_models::{ + payments::{ + AmountInfo, ApplePayPaymentRequest, ApplePaySessionResponse, + ApplepayCombinedSessionTokenData, ApplepaySessionTokenData, ApplepaySessionTokenMetadata, + ApplepaySessionTokenResponse, NextActionCall, NoThirdPartySdkSessionResponse, + SdkNextAction, SessionToken, + }, + webhooks::IncomingWebhookEvent, +}; use base64::Engine; +use common_enums::{enums, CountryAlpha2}; use common_utils::{ + consts::{APPLEPAY_VALIDATION_URL, BASE64_ENGINE}, errors::CustomResult, - ext_traits::{ByteSliceExt, StringExt, ValueExt}, + ext_traits::{ByteSliceExt, Encode, OptionExt, StringExt, ValueExt}, pii::Email, types::StringMajorUnit, }; use error_stack::ResultExt; -use masking::{ExposeInterface, PeekInterface}; +use hyperswitch_domain_models::{ + address::AddressDetails, + payment_method_data::{self, PaymentMethodData, WalletData}, + router_data::{ConnectorAuthType, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types, +}; +use hyperswitch_interfaces::errors; +use masking::{ExposeInterface, PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use serde_json::Value; use crate::{ - connector::utils::{ - self, AddressDetailsData, ApplePay, CardData, PaymentsAuthorizeRequestData, - PaymentsCompleteAuthorizeRequestData, RouterData, + types::{PaymentsSessionResponseRouterData, RefundsResponseRouterData, ResponseRouterData}, + utils::{ + self, AddressDetailsData, ApplePay, CardData as _, ForeignTryFrom, + PaymentsAuthorizeRequestData, PaymentsCompleteAuthorizeRequestData, RouterData as _, }, - consts, - core::errors, - pii::Secret, - types::{ - self, api, domain, - storage::enums, - transformers::{ForeignFrom, ForeignTryFrom}, - }, - utils::{Encode, OptionExt}, }; const DISPLAY_METADATA: &str = "Y"; @@ -150,7 +162,7 @@ pub struct EncodedPaymentToken { #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct BillingDetails { - country_code: Option, + country_code: Option, address_lines: Option>>, family_name: Option>, given_name: Option>, @@ -210,26 +222,28 @@ impl TryFrom<&BluesnapRouterData<&types::PaymentsAuthorizeRouterData>> item: &BluesnapRouterData<&types::PaymentsAuthorizeRouterData>, ) -> Result { match item.router_data.request.payment_method_data { - domain::PaymentMethodData::Card(ref ccard) => Ok(Self { + PaymentMethodData::Card(ref ccard) => Ok(Self { cc_number: ccard.card_number.clone(), exp_date: ccard.get_expiry_date_as_mmyyyy("/"), }), - 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::RealTimePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::CardRedirect(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + PaymentMethodData::Wallet(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::CardRedirect(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( "Selected payment method via Token flow through bluesnap".to_string(), ) @@ -254,7 +268,7 @@ impl TryFrom<&BluesnapRouterData<&types::PaymentsAuthorizeRouterData>> for Blues .metadata .as_ref() .map(|metadata| BluesnapMetadata { - meta_data: Vec::::foreign_from(metadata.to_owned()), + meta_data: convert_metadata_to_request_metadata(metadata.to_owned()), }); let (payment_method, card_holder_info) = match item @@ -263,7 +277,7 @@ impl TryFrom<&BluesnapRouterData<&types::PaymentsAuthorizeRouterData>> for Blues .payment_method_data .clone() { - domain::PaymentMethodData::Card(ref ccard) => Ok(( + PaymentMethodData::Card(ref ccard) => Ok(( PaymentMethodDetails::CreditCard(Card { card_number: ccard.card_number.clone(), expiration_month: ccard.card_exp_month.clone(), @@ -275,8 +289,8 @@ impl TryFrom<&BluesnapRouterData<&types::PaymentsAuthorizeRouterData>> for Blues item.router_data.request.get_email()?, )?, )), - domain::PaymentMethodData::Wallet(wallet_data) => match wallet_data { - domain::WalletData::GooglePay(payment_method_data) => { + PaymentMethodData::Wallet(wallet_data) => match wallet_data { + WalletData::GooglePay(payment_method_data) => { let gpay_object = BluesnapGooglePayObject { payment_method_data: utils::GooglePayWalletData::from(payment_method_data), } @@ -285,14 +299,12 @@ impl TryFrom<&BluesnapRouterData<&types::PaymentsAuthorizeRouterData>> for Blues Ok(( PaymentMethodDetails::Wallet(BluesnapWallet { wallet_type: BluesnapWalletTypes::GooglePay, - encoded_payment_token: Secret::new( - consts::BASE64_ENGINE.encode(gpay_object), - ), + encoded_payment_token: Secret::new(BASE64_ENGINE.encode(gpay_object)), }), None, )) } - domain::WalletData::ApplePay(payment_method_data) => { + WalletData::ApplePay(payment_method_data) => { let apple_pay_payment_data = payment_method_data.get_applepay_decoded_payment_data()?; let apple_pay_payment_data: ApplePayEncodedPaymentData = apple_pay_payment_data @@ -344,7 +356,7 @@ impl TryFrom<&BluesnapRouterData<&types::PaymentsAuthorizeRouterData>> for Blues PaymentMethodDetails::Wallet(BluesnapWallet { wallet_type: BluesnapWalletTypes::ApplePay, encoded_payment_token: Secret::new( - consts::BASE64_ENGINE.encode(apple_pay_object), + BASE64_ENGINE.encode(apple_pay_object), ), }), get_card_holder_info( @@ -353,49 +365,52 @@ impl TryFrom<&BluesnapRouterData<&types::PaymentsAuthorizeRouterData>> for Blues )?, )) } - domain::WalletData::AliPayQr(_) - | domain::WalletData::AliPayRedirect(_) - | domain::WalletData::AliPayHkRedirect(_) - | domain::WalletData::MomoRedirect(_) - | domain::WalletData::KakaoPayRedirect(_) - | domain::WalletData::GoPayRedirect(_) - | domain::WalletData::GcashRedirect(_) - | domain::WalletData::ApplePayRedirect(_) - | domain::WalletData::ApplePayThirdPartySdk(_) - | domain::WalletData::DanaRedirect {} - | domain::WalletData::GooglePayRedirect(_) - | domain::WalletData::GooglePayThirdPartySdk(_) - | domain::WalletData::MbWayRedirect(_) - | domain::WalletData::MobilePayRedirect(_) - | domain::WalletData::PaypalRedirect(_) - | domain::WalletData::PaypalSdk(_) - | domain::WalletData::SamsungPay(_) - | domain::WalletData::TwintRedirect {} - | domain::WalletData::VippsRedirect {} - | domain::WalletData::TouchNGoRedirect(_) - | domain::WalletData::WeChatPayRedirect(_) - | domain::WalletData::CashappQr(_) - | domain::WalletData::SwishQr(_) - | domain::WalletData::WeChatPayQr(_) - | domain::WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( + WalletData::AliPayQr(_) + | WalletData::AliPayRedirect(_) + | WalletData::AliPayHkRedirect(_) + | WalletData::MomoRedirect(_) + | WalletData::KakaoPayRedirect(_) + | WalletData::GoPayRedirect(_) + | WalletData::GcashRedirect(_) + | WalletData::ApplePayRedirect(_) + | WalletData::ApplePayThirdPartySdk(_) + | WalletData::DanaRedirect {} + | WalletData::GooglePayRedirect(_) + | WalletData::GooglePayThirdPartySdk(_) + | WalletData::MbWayRedirect(_) + | WalletData::MobilePayRedirect(_) + | WalletData::PaypalRedirect(_) + | WalletData::PaypalSdk(_) + | WalletData::Paze(_) + | WalletData::SamsungPay(_) + | WalletData::TwintRedirect {} + | WalletData::VippsRedirect {} + | WalletData::TouchNGoRedirect(_) + | WalletData::WeChatPayRedirect(_) + | WalletData::CashappQr(_) + | WalletData::SwishQr(_) + | WalletData::WeChatPayQr(_) + | WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("bluesnap"), )), }, - domain::PaymentMethodData::PayLater(_) - | domain::PaymentMethodData::BankRedirect(_) - | domain::PaymentMethodData::BankDebit(_) - | domain::PaymentMethodData::BankTransfer(_) - | domain::PaymentMethodData::Crypto(_) - | domain::PaymentMethodData::MandatePayment - | domain::PaymentMethodData::Reward - | domain::PaymentMethodData::RealTimePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::CardRedirect(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::CardRedirect(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("bluesnap"), )) @@ -416,8 +431,8 @@ impl TryFrom<&BluesnapRouterData<&types::PaymentsAuthorizeRouterData>> for Blues } } -impl From for ApplepayPaymentMethod { - fn from(item: domain::ApplepayPaymentMethod) -> Self { +impl From for ApplepayPaymentMethod { + fn from(item: payment_method_data::ApplepayPaymentMethod) -> Self { Self { display_name: item.display_name, network: item.network, @@ -432,27 +447,23 @@ impl TryFrom<&types::PaymentsSessionRouterData> for BluesnapCreateWalletToken { let apple_pay_metadata = item.get_connector_meta()?.expose(); let applepay_metadata = apple_pay_metadata .clone() - .parse_value::( - "ApplepayCombinedSessionTokenData", - ) + .parse_value::("ApplepayCombinedSessionTokenData") .map(|combined_metadata| { - payments::ApplepaySessionTokenMetadata::ApplePayCombined( - combined_metadata.apple_pay_combined, - ) + ApplepaySessionTokenMetadata::ApplePayCombined(combined_metadata.apple_pay_combined) }) .or_else(|_| { apple_pay_metadata - .parse_value::("ApplepaySessionTokenData") + .parse_value::("ApplepaySessionTokenData") .map(|old_metadata| { - payments::ApplepaySessionTokenMetadata::ApplePay(old_metadata.apple_pay) + ApplepaySessionTokenMetadata::ApplePay(old_metadata.apple_pay) }) }) .change_context(errors::ConnectorError::ParsingFailed)?; let session_token_data = match applepay_metadata { - payments::ApplepaySessionTokenMetadata::ApplePay(apple_pay_data) => { + ApplepaySessionTokenMetadata::ApplePay(apple_pay_data) => { Ok(apple_pay_data.session_token_data) } - payments::ApplepaySessionTokenMetadata::ApplePayCombined(_apple_pay_combined_data) => { + ApplepaySessionTokenMetadata::ApplePayCombined(_apple_pay_combined_data) => { Err(errors::ConnectorError::FlowNotSupported { flow: "apple pay combined".to_string(), connector: "bluesnap".to_string(), @@ -467,7 +478,7 @@ impl TryFrom<&types::PaymentsSessionRouterData> for BluesnapCreateWalletToken { Ok(Self { wallet_type: "APPLE_PAY".to_string(), - validation_url: consts::APPLEPAY_VALIDATION_URL.to_string().into(), + validation_url: APPLEPAY_VALIDATION_URL.to_string().into(), domain_name, display_name: Some(session_token_data.display_name), }) @@ -476,92 +487,87 @@ impl TryFrom<&types::PaymentsSessionRouterData> for BluesnapCreateWalletToken { impl ForeignTryFrom<( - types::PaymentsSessionResponseRouterData, + PaymentsSessionResponseRouterData, StringMajorUnit, )> for types::PaymentsSessionRouterData { type Error = error_stack::Report; fn foreign_try_from( (item, apple_pay_amount): ( - types::PaymentsSessionResponseRouterData, + PaymentsSessionResponseRouterData, StringMajorUnit, ), ) -> Result { let response = &item.response; - let wallet_token = consts::BASE64_ENGINE + let wallet_token = BASE64_ENGINE .decode(response.wallet_token.clone().expose()) .change_context(errors::ConnectorError::ResponseHandlingFailed)?; - let session_response: payments::NoThirdPartySdkSessionResponse = wallet_token + let session_response: NoThirdPartySdkSessionResponse = wallet_token .parse_struct("NoThirdPartySdkSessionResponse") .change_context(errors::ConnectorError::ParsingFailed)?; let metadata = item.data.get_connector_meta()?.expose(); let applepay_metadata = metadata .clone() - .parse_value::( - "ApplepayCombinedSessionTokenData", - ) + .parse_value::("ApplepayCombinedSessionTokenData") .map(|combined_metadata| { - payments::ApplepaySessionTokenMetadata::ApplePayCombined( - combined_metadata.apple_pay_combined, - ) + ApplepaySessionTokenMetadata::ApplePayCombined(combined_metadata.apple_pay_combined) }) .or_else(|_| { metadata - .parse_value::("ApplepaySessionTokenData") + .parse_value::("ApplepaySessionTokenData") .map(|old_metadata| { - payments::ApplepaySessionTokenMetadata::ApplePay(old_metadata.apple_pay) + ApplepaySessionTokenMetadata::ApplePay(old_metadata.apple_pay) }) }) .change_context(errors::ConnectorError::ParsingFailed)?; let (payment_request_data, session_token_data) = match applepay_metadata { - payments::ApplepaySessionTokenMetadata::ApplePayCombined(_apple_pay_combined) => { + ApplepaySessionTokenMetadata::ApplePayCombined(_apple_pay_combined) => { Err(errors::ConnectorError::FlowNotSupported { flow: "apple pay combined".to_string(), connector: "bluesnap".to_string(), }) } - payments::ApplepaySessionTokenMetadata::ApplePay(apple_pay) => { + ApplepaySessionTokenMetadata::ApplePay(apple_pay) => { Ok((apple_pay.payment_request_data, apple_pay.session_token_data)) } }?; Ok(Self { - response: Ok(types::PaymentsResponseData::SessionResponse { - session_token: api::SessionToken::ApplePay(Box::new( - payments::ApplepaySessionTokenResponse { - session_token_data: Some( - payments::ApplePaySessionResponse::NoThirdPartySdk(session_response), - ), - payment_request_data: Some(payments::ApplePayPaymentRequest { - country_code: item.data.get_billing_country()?, - currency_code: item.data.request.currency, - total: payments::AmountInfo { - label: payment_request_data.label, - total_type: Some("final".to_string()), - amount: apple_pay_amount, - }, - merchant_capabilities: Some(payment_request_data.merchant_capabilities), - supported_networks: Some(payment_request_data.supported_networks), - merchant_identifier: Some(session_token_data.merchant_identifier), - required_billing_contact_fields: None, - required_shipping_contact_fields: None, - }), - connector: "bluesnap".to_string(), - delayed_session_token: false, - sdk_next_action: { - payments::SdkNextAction { - next_action: payments::NextActionCall::Confirm, - } + response: Ok(PaymentsResponseData::SessionResponse { + session_token: SessionToken::ApplePay(Box::new(ApplepaySessionTokenResponse { + session_token_data: Some(ApplePaySessionResponse::NoThirdPartySdk( + session_response, + )), + payment_request_data: Some(ApplePayPaymentRequest { + country_code: item.data.get_billing_country()?, + currency_code: item.data.request.currency, + total: AmountInfo { + label: payment_request_data.label, + total_type: Some("final".to_string()), + amount: apple_pay_amount, }, - connector_reference_id: None, - connector_sdk_public_key: None, - connector_merchant_id: None, + merchant_capabilities: Some(payment_request_data.merchant_capabilities), + supported_networks: Some(payment_request_data.supported_networks), + merchant_identifier: Some(session_token_data.merchant_identifier), + required_billing_contact_fields: None, + required_shipping_contact_fields: None, + recurring_payment_request: None, + }), + connector: "bluesnap".to_string(), + delayed_session_token: false, + sdk_next_action: { + SdkNextAction { + next_action: NextActionCall::Confirm, + } }, - )), + connector_reference_id: None, + connector_sdk_public_key: None, + connector_merchant_id: None, + })), }), ..item.data }) @@ -607,7 +613,7 @@ impl TryFrom<&BluesnapRouterData<&types::PaymentsCompleteAuthorizeRouterData>> .metadata .as_ref() .map(|metadata| BluesnapMetadata { - meta_data: Vec::::foreign_from(metadata.to_owned()), + meta_data: convert_metadata_to_request_metadata(metadata.to_owned()), }); let token = item @@ -740,10 +746,10 @@ pub struct BluesnapAuthType { pub(super) key1: Secret, } -impl TryFrom<&types::ConnectorAuthType> for BluesnapAuthType { +impl TryFrom<&ConnectorAuthType> for BluesnapAuthType { type Error = error_stack::Report; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { - if let types::ConnectorAuthType::BodyKey { api_key, key1 } = auth_type { + fn try_from(auth_type: &ConnectorAuthType) -> Result { + if let ConnectorAuthType::BodyKey { api_key, key1 } = auth_type { Ok(Self { api_key: api_key.to_owned(), key1: key1.to_owned(), @@ -850,30 +856,24 @@ pub struct ProcessingInfoResponse { pub network_transaction_id: Option>, } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< - F, - BluesnapPaymentsResponse, - T, - types::PaymentsResponseData, - >, + item: ResponseRouterData, ) -> Result { Ok(Self { status: enums::AttemptStatus::foreign_try_from(( item.response.card_transaction_type, item.response.processing_info.processing_status, ))?, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( item.response.transaction_id.clone(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(item.response.transaction_id), @@ -917,15 +917,15 @@ pub struct RefundResponse { refund_status: BluesnapRefundStatus, } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item.response.transaction_id.clone(), refund_status: enums::RefundStatus::from( item.response.processing_info.processing_status, @@ -936,15 +936,15 @@ impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item.response.refund_transaction_id.to_string(), refund_status: enums::RefundStatus::from(item.response.refund_status), }), @@ -998,7 +998,7 @@ pub enum BluesnapWebhookEvents { Unknown, } -impl TryFrom for api::IncomingWebhookEvent { +impl TryFrom for IncomingWebhookEvent { type Error = error_stack::Report; fn try_from(details: BluesnapWebhookObjectEventType) -> Result { match details.transaction_type { @@ -1030,7 +1030,7 @@ impl TryFrom for api::IncomingWebhookEvent { #[serde(rename_all = "camelCase")] pub struct BluesnapDisputeWebhookBody { pub invoice_charge_amount: f64, - pub currency: diesel_models::enums::Currency, + pub currency: enums::Currency, pub reversal_reason: Option, pub reversal_ref_num: String, pub cb_status: String, @@ -1120,7 +1120,7 @@ pub enum BluesnapErrors { } fn get_card_holder_info( - address: &api::AddressDetails, + address: &AddressDetails, email: Email, ) -> CustomResult, errors::ConnectorError> { let first_name = address.get_first_name()?; @@ -1140,18 +1140,16 @@ impl From for utils::ErrorCodeAndMessage { } } -impl ForeignFrom for Vec { - fn foreign_from(metadata: Value) -> Self { - let hashmap: HashMap, Option> = - serde_json::from_str(&metadata.to_string()).unwrap_or(HashMap::new()); - let mut vector: Self = Self::new(); - for (key, value) in hashmap { - vector.push(RequestMetadata { - meta_key: key, - meta_value: value.map(|field_value| field_value.to_string()), - is_visible: Some(DISPLAY_METADATA.to_string()), - }); - } - vector +fn convert_metadata_to_request_metadata(metadata: Value) -> Vec { + let hashmap: HashMap, Option> = + serde_json::from_str(&metadata.to_string()).unwrap_or(HashMap::new()); + let mut vector = Vec::::new(); + for (key, value) in hashmap { + vector.push(RequestMetadata { + meta_key: key, + meta_value: value.map(|field_value| field_value.to_string()), + is_visible: Some(DISPLAY_METADATA.to_string()), + }); } + vector } diff --git a/crates/router/src/connector/boku.rs b/crates/hyperswitch_connectors/src/connectors/boku.rs similarity index 70% rename from crates/router/src/connector/boku.rs rename to crates/hyperswitch_connectors/src/connectors/boku.rs index a132094693a1..68195979874b 100644 --- a/crates/router/src/connector/boku.rs +++ b/crates/hyperswitch_connectors/src/connectors/boku.rs @@ -1,38 +1,55 @@ pub mod transformers; +use api_models::webhooks::{IncomingWebhookEvent, ObjectReferenceId}; +use common_enums::enums; use common_utils::{ - ext_traits::XmlExt, - request::RequestContent, + errors::CustomResult, + ext_traits::{BytesExt, OptionExt, XmlExt}, + request::{Method, Request, RequestBuilder, RequestContent}, types::{AmountConvertor, MinorUnit, MinorUnitForConnector}, }; -use diesel_models::enums; use error_stack::{report, Report, ResultExt}; -use masking::{ExposeInterface, PeekInterface, Secret, WithType}; +use hyperswitch_domain_models::{ + router_data::{AccessToken, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, + RefundSyncRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + consts::NO_ERROR_CODE, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, +}; +use masking::{ExposeInterface, Mask, PeekInterface, Secret, WithType}; use ring::hmac; -use router_env::metrics::add_attributes; -use roxmltree; +use router_env::logger; use time::OffsetDateTime; use transformers as boku; -use super::utils as connector_utils; use crate::{ - configs::settings, - consts, - core::errors::{self, CustomResult}, - events::connector_api_logs::ConnectorEvent, - headers, logger, - routes::metrics, - services::{ - self, - request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, - }, - types::{ - self, - api::{self, ConnectorCommon, ConnectorCommonExt}, - ErrorResponse, Response, - }, - utils::{BytesExt, OptionExt}, + constants::{headers, UNSUPPORTED_ERROR_MESSAGE}, + metrics, + types::ResponseRouterData, + utils::{construct_not_supported_error_report, convert_amount}, }; #[derive(Clone)] @@ -61,12 +78,8 @@ impl api::RefundExecute for Boku {} impl api::RefundSync for Boku {} impl api::PaymentToken for Boku {} -impl - ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Boku +impl ConnectorIntegration + for Boku { // Not Implemented (R) } @@ -77,9 +90,9 @@ where { fn build_headers( &self, - req: &types::RouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let connector_auth = boku::BokuAuthType::try_from(&req.connector_auth_type)?; let boku_url = Self::get_url(self, req, connectors)?; @@ -125,7 +138,7 @@ impl ConnectorCommon for Boku { "text/xml;charset=utf-8" } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.boku.base_url.as_ref() } @@ -158,48 +171,36 @@ impl ConnectorCommon for Boku { } impl ConnectorValidation for Boku { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( - connector_utils::construct_not_supported_error_report(capture_method, self.id()), + construct_not_supported_error_report(capture_method, self.id()), ), } } } -impl ConnectorIntegration - for Boku -{ +impl ConnectorIntegration for Boku { //TODO: implement sessions flow } -impl ConnectorIntegration - for Boku -{ -} +impl ConnectorIntegration for Boku {} -impl - ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Boku -{ +impl ConnectorIntegration for Boku { fn build_request( &self, - _req: &types::RouterData< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - >, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err( errors::ConnectorError::NotImplemented("Setup Mandate flow for Boku".to_string()) .into(), @@ -207,19 +208,17 @@ impl } } -impl ConnectorIntegration - for Boku -{ +impl ConnectorIntegration for Boku { fn get_headers( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } - fn get_http_method(&self) -> services::Method { - services::Method::Post + fn get_http_method(&self) -> Method { + Method::Post } fn get_content_type(&self) -> &'static str { @@ -228,8 +227,8 @@ impl ConnectorIntegration CustomResult { let boku_url = get_country_url( req.connector_meta_data.clone(), @@ -241,10 +240,10 @@ impl ConnectorIntegration CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_amount, req.request.currency, @@ -257,12 +256,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsAuthorizeType::get_url( self, req, connectors, )?) @@ -279,10 +278,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response_data = String::from_utf8(res.response.to_vec()) .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; @@ -291,7 +290,7 @@ impl ConnectorIntegration - for Boku -{ +impl ConnectorIntegration for Boku { fn get_headers( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -324,8 +321,8 @@ impl ConnectorIntegration CustomResult { let boku_url = get_country_url( req.connector_meta_data.clone(), @@ -337,8 +334,8 @@ impl ConnectorIntegration CustomResult { let connector_req = boku::BokuPsyncRequest::try_from(req)?; Ok(RequestContent::Xml(Box::new(connector_req))) @@ -346,12 +343,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) + RequestBuilder::new() + .method(Method::Get) .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) @@ -364,10 +361,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response_data = String::from_utf8(res.response.to_vec()) .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; @@ -376,7 +373,7 @@ impl ConnectorIntegration - for Boku -{ +impl ConnectorIntegration for Boku { fn get_headers( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -409,28 +404,28 @@ impl ConnectorIntegration CustomResult { Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) } fn get_request_body( &self, - _req: &types::PaymentsCaptureRouterData, - _connectors: &settings::Connectors, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, ) -> CustomResult { Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) } fn build_request( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsCaptureType::get_headers( @@ -445,10 +440,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response_data = String::from_utf8(res.response.to_vec()) .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; @@ -457,7 +452,7 @@ impl ConnectorIntegration - for Boku -{ -} +impl ConnectorIntegration for Boku {} -impl ConnectorIntegration for Boku { +impl ConnectorIntegration for Boku { fn get_headers( &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -493,8 +485,8 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, + req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { let boku_url = get_country_url( req.connector_meta_data.clone(), @@ -506,10 +498,10 @@ impl ConnectorIntegration, - _connectors: &settings::Connectors, + req: &RefundsRouterData, + _connectors: &Connectors, ) -> CustomResult { - let refund_amount = connector_utils::convert_amount( + let refund_amount = convert_amount( self.amount_converter, req.request.minor_refund_amount, req.request.currency, @@ -522,11 +514,11 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) .url(&types::RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundExecuteType::get_headers( @@ -541,10 +533,10 @@ impl ConnectorIntegration, + data: &RefundsRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult, errors::ConnectorError> { let response: boku::RefundResponse = res .response .parse_struct("boku RefundResponse") @@ -553,7 +545,7 @@ impl ConnectorIntegration for Boku { +impl ConnectorIntegration for Boku { fn get_headers( &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -584,8 +576,8 @@ impl ConnectorIntegration CustomResult { let boku_url = get_country_url( req.connector_meta_data.clone(), @@ -597,8 +589,8 @@ impl ConnectorIntegration CustomResult { let connector_req = boku::BokuRsyncRequest::try_from(req)?; Ok(RequestContent::Xml(Box::new(connector_req))) @@ -606,12 +598,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) + RequestBuilder::new() + .method(Method::Get) .url(&types::RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundSyncType::get_headers(self, req, connectors)?) @@ -624,17 +616,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: boku::BokuRsyncResponse = res .response .parse_struct("boku BokuRsyncResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -651,24 +643,24 @@ impl ConnectorIntegration, - ) -> CustomResult { + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_event_type( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_resource_object( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } @@ -690,11 +682,8 @@ fn get_xml_deserialized( res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { - metrics::RESPONSE_DESERIALIZATION_FAILURE.add( - &metrics::CONTEXT, - 1, - &add_attributes([("connector", "boku")]), - ); + metrics::CONNECTOR_RESPONSE_DESERIALIZATION_FAILURE + .add(1, router_env::metric_attributes!(("connector", "boku"))); let response_data = String::from_utf8(res.response.to_vec()) .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; @@ -711,8 +700,8 @@ fn get_xml_deserialized( logger::error!("UNEXPECTED RESPONSE FROM CONNECTOR: {}", response_data); Ok(ErrorResponse { status_code: res.status_code, - code: consts::NO_ERROR_CODE.to_string(), - message: consts::UNSUPPORTED_ERROR_MESSAGE.to_string(), + code: NO_ERROR_CODE.to_string(), + message: UNSUPPORTED_ERROR_MESSAGE.to_string(), reason: Some(response_data), attempt_status: None, connector_transaction_id: None, @@ -720,3 +709,5 @@ fn get_xml_deserialized( } } } + +impl ConnectorSpecifications for Boku {} diff --git a/crates/router/src/connector/boku/transformers.rs b/crates/hyperswitch_connectors/src/connectors/boku/transformers.rs similarity index 73% rename from crates/router/src/connector/boku/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/boku/transformers.rs index 5fd6b0e6a2eb..da5f775489c3 100644 --- a/crates/router/src/connector/boku/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/boku/transformers.rs @@ -1,16 +1,24 @@ use std::fmt; -use common_utils::types::MinorUnit; +use common_enums::enums; +use common_utils::{request::Method, types::MinorUnit}; +use hyperswitch_domain_models::{ + payment_method_data::{PaymentMethodData, WalletData}, + router_data::{ConnectorAuthType, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData}, + types::{self, RefundsRouterData}, +}; +use hyperswitch_interfaces::errors; use masking::Secret; use serde::{Deserialize, Serialize}; use url::Url; use uuid::Uuid; use crate::{ - connector::utils::{self, AddressDetailsData, RouterData}, - core::errors, - services::{self, RedirectForm}, - types::{self, api, domain, storage::enums}, + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::{self, AddressDetailsData, RouterData as _}, }; #[derive(Debug, Serialize)] @@ -96,23 +104,25 @@ impl TryFrom<&BokuRouterData<&types::PaymentsAuthorizeRouterData>> for BokuPayme item: &BokuRouterData<&types::PaymentsAuthorizeRouterData>, ) -> Result { match item.router_data.request.payment_method_data.clone() { - domain::PaymentMethodData::Wallet(wallet_data) => Self::try_from((item, &wallet_data)), - domain::PaymentMethodData::Card(_) - | 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::RealTimePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + PaymentMethodData::Wallet(wallet_data) => Self::try_from((item, &wallet_data)), + PaymentMethodData::Card(_) + | PaymentMethodData::CardRedirect(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("boku"), ))? @@ -124,14 +134,14 @@ impl TryFrom<&BokuRouterData<&types::PaymentsAuthorizeRouterData>> for BokuPayme impl TryFrom<( &BokuRouterData<&types::PaymentsAuthorizeRouterData>, - &domain::WalletData, + &WalletData, )> for BokuPaymentsRequest { type Error = error_stack::Report; fn try_from( value: ( &BokuRouterData<&types::PaymentsAuthorizeRouterData>, - &domain::WalletData, + &WalletData, ), ) -> Result { let (item_router_data, wallet_data) = value; @@ -160,35 +170,36 @@ impl } } -fn get_wallet_type(wallet_data: &domain::WalletData) -> Result { +fn get_wallet_type(wallet_data: &WalletData) -> Result { match wallet_data { - domain::WalletData::DanaRedirect { .. } => Ok(BokuPaymentType::Dana.to_string()), - domain::WalletData::MomoRedirect { .. } => Ok(BokuPaymentType::Momo.to_string()), - domain::WalletData::GcashRedirect { .. } => Ok(BokuPaymentType::Gcash.to_string()), - domain::WalletData::GoPayRedirect { .. } => Ok(BokuPaymentType::GoPay.to_string()), - domain::WalletData::KakaoPayRedirect { .. } => Ok(BokuPaymentType::Kakaopay.to_string()), - domain::WalletData::AliPayQr(_) - | domain::WalletData::AliPayRedirect(_) - | domain::WalletData::AliPayHkRedirect(_) - | domain::WalletData::ApplePay(_) - | domain::WalletData::ApplePayRedirect(_) - | domain::WalletData::ApplePayThirdPartySdk(_) - | domain::WalletData::GooglePay(_) - | domain::WalletData::GooglePayRedirect(_) - | domain::WalletData::GooglePayThirdPartySdk(_) - | domain::WalletData::MbWayRedirect(_) - | domain::WalletData::MobilePayRedirect(_) - | domain::WalletData::PaypalRedirect(_) - | domain::WalletData::PaypalSdk(_) - | domain::WalletData::SamsungPay(_) - | domain::WalletData::TwintRedirect {} - | domain::WalletData::VippsRedirect {} - | domain::WalletData::TouchNGoRedirect(_) - | domain::WalletData::WeChatPayRedirect(_) - | domain::WalletData::WeChatPayQr(_) - | domain::WalletData::CashappQr(_) - | domain::WalletData::SwishQr(_) - | domain::WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( + WalletData::DanaRedirect { .. } => Ok(BokuPaymentType::Dana.to_string()), + WalletData::MomoRedirect { .. } => Ok(BokuPaymentType::Momo.to_string()), + WalletData::GcashRedirect { .. } => Ok(BokuPaymentType::Gcash.to_string()), + WalletData::GoPayRedirect { .. } => Ok(BokuPaymentType::GoPay.to_string()), + WalletData::KakaoPayRedirect { .. } => Ok(BokuPaymentType::Kakaopay.to_string()), + WalletData::AliPayQr(_) + | WalletData::AliPayRedirect(_) + | WalletData::AliPayHkRedirect(_) + | WalletData::ApplePay(_) + | WalletData::ApplePayRedirect(_) + | WalletData::ApplePayThirdPartySdk(_) + | WalletData::GooglePay(_) + | WalletData::GooglePayRedirect(_) + | WalletData::GooglePayThirdPartySdk(_) + | WalletData::MbWayRedirect(_) + | WalletData::MobilePayRedirect(_) + | WalletData::PaypalRedirect(_) + | WalletData::PaypalSdk(_) + | WalletData::Paze(_) + | WalletData::SamsungPay(_) + | WalletData::TwintRedirect {} + | WalletData::VippsRedirect {} + | WalletData::TouchNGoRedirect(_) + | WalletData::WeChatPayRedirect(_) + | WalletData::WeChatPayQr(_) + | WalletData::CashappQr(_) + | WalletData::SwishQr(_) + | WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("boku"), )), } @@ -199,11 +210,11 @@ pub struct BokuAuthType { pub(super) key_id: Secret, } -impl TryFrom<&types::ConnectorAuthType> for BokuAuthType { +impl TryFrom<&ConnectorAuthType> for BokuAuthType { type Error = error_stack::Report; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { + fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - types::ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { + ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { merchant_id: key1.to_owned(), key_id: api_key.to_owned(), }), @@ -283,12 +294,12 @@ pub struct SingleChargeResponseData { charge_id: String, } -impl TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { let (status, transaction_id, redirection_data) = match item.response { BokuResponse::BeginSingleChargeResponse(response) => get_authorize_response(response), @@ -297,10 +308,10 @@ impl TryFrom Ok(hosted_value .redirect_url - .map(|url| RedirectForm::from((url, services::Method::Get)))), + .map(|url| RedirectForm::from((url, Method::Get)))), None => Err(errors::ConnectorError::MissingConnectorRedirectionPayload { field_name: "redirect_url", }), @@ -369,9 +380,9 @@ impl fmt::Display for BokuRefundReasonCode { } } -impl TryFrom<&BokuRouterData<&types::RefundsRouterData>> for BokuRefundRequest { +impl TryFrom<&BokuRouterData<&RefundsRouterData>> for BokuRefundRequest { type Error = error_stack::Report; - fn try_from(item: &BokuRouterData<&types::RefundsRouterData>) -> Result { + fn try_from(item: &BokuRouterData<&RefundsRouterData>) -> Result { let auth_type = BokuAuthType::try_from(&item.router_data.connector_auth_type)?; let payment_data = Self { refund_amount: item.amount, @@ -405,15 +416,13 @@ fn get_refund_status(status: String) -> enums::RefundStatus { } } -impl TryFrom> - for types::RefundsRouterData -{ +impl TryFrom> for RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item.response.charge_id, refund_status: get_refund_status(item.response.refund_status), }), @@ -466,15 +475,13 @@ pub struct SingleRefundResponseData { refund_id: String, } -impl TryFrom> - for types::RefundsRouterData -{ +impl TryFrom> for RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item.response.refunds.refund.refund_id, refund_status: get_refund_status(item.response.refunds.refund.refund_status), }), @@ -492,7 +499,8 @@ pub struct BokuErrorResponse { } fn get_hosted_data(item: &types::PaymentsAuthorizeRouterData) -> Option { - item.return_url + item.request + .router_return_url .clone() .map(|url| BokuHostedData { forward_url: url }) } diff --git a/crates/hyperswitch_connectors/src/connectors/cashtocode.rs b/crates/hyperswitch_connectors/src/connectors/cashtocode.rs index c72b63aebc05..cc7ac3812374 100644 --- a/crates/hyperswitch_connectors/src/connectors/cashtocode.rs +++ b/crates/hyperswitch_connectors/src/connectors/cashtocode.rs @@ -25,12 +25,15 @@ use hyperswitch_domain_models::{ types::{PaymentsAuthorizeRouterData, PaymentsSyncRouterData}, }; use hyperswitch_interfaces::{ - api::{self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorValidation}, + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, configs::Connectors, errors, events::connector_api_logs::ConnectorEvent, types::{PaymentsAuthorizeType, Response}, - webhooks, + webhooks::{self, IncomingWebhookFlowError}, }; use masking::{Mask, PeekInterface, Secret}; use transformers as cashtocode; @@ -64,7 +67,7 @@ impl api::RefundExecute for Cashtocode {} impl api::RefundSync for Cashtocode {} fn get_b64_auth_cashtocode( - payment_method_type: &Option, + payment_method_type: Option, auth_type: &transformers::CashtocodeAuth, ) -> CustomResult)>, errors::ConnectorError> { fn construct_basic_auth( @@ -148,14 +151,17 @@ impl ConnectorCommon for Cashtocode { } impl ConnectorValidation for Cashtocode { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( utils::construct_not_supported_error_report(capture_method, self.id()), ), @@ -202,7 +208,7 @@ impl ConnectorIntegration, + _error_kind: Option, ) -> CustomResult, errors::ConnectorError> { let status = "EXECUTED".to_string(); let obj: transformers::CashtocodePaymentsSyncResponse = request @@ -449,3 +456,5 @@ impl ConnectorIntegration for Cashtoc impl ConnectorIntegration for Cashtocode { // default implementation of build_request method will be executed } + +impl ConnectorSpecifications for Cashtocode {} diff --git a/crates/hyperswitch_connectors/src/connectors/cashtocode/transformers.rs b/crates/hyperswitch_connectors/src/connectors/cashtocode/transformers.rs index 494931276210..7a70d97a102c 100644 --- a/crates/hyperswitch_connectors/src/connectors/cashtocode/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/cashtocode/transformers.rs @@ -203,7 +203,7 @@ pub struct CashtocodePaymentsSyncResponse { } fn get_redirect_form_data( - payment_method_type: &enums::PaymentMethodType, + payment_method_type: enums::PaymentMethodType, response_data: CashtocodePaymentsResponseData, ) -> CustomResult { match payment_method_type { @@ -260,7 +260,6 @@ impl .data .request .payment_method_type - .as_ref() .ok_or(errors::ConnectorError::MissingPaymentMethodType)?; let redirection_data = get_redirect_form_data(payment_method_type, response_data)?; ( @@ -269,8 +268,8 @@ impl resource_id: ResponseId::ConnectorTransactionId( item.data.attempt_id.clone(), ), - redirection_data: Some(redirection_data), - mandate_reference: None, + redirection_data: Box::new(Some(redirection_data)), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -302,8 +301,8 @@ impl TryFrom, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( utils::construct_not_supported_error_report(capture_method, self.id()), ), @@ -438,3 +444,5 @@ impl webhooks::IncomingWebhook for Coinbase { Ok(Box::new(notif.event)) } } + +impl ConnectorSpecifications for Coinbase {} diff --git a/crates/hyperswitch_connectors/src/connectors/coinbase/transformers.rs b/crates/hyperswitch_connectors/src/connectors/coinbase/transformers.rs index 3633c366c4cb..8ac5f86247ca 100644 --- a/crates/hyperswitch_connectors/src/connectors/coinbase/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/coinbase/transformers.rs @@ -146,8 +146,8 @@ impl TryFrom> | PaymentMethodData::Reward {} | PaymentMethodData::RealTimePayment(_) | PaymentMethodData::Upi(_) + | PaymentMethodData::MobilePayment(_) | PaymentMethodData::Voucher(_) | PaymentMethodData::GiftCard(_) | PaymentMethodData::OpenBanking(_) | PaymentMethodData::CardToken(_) - | PaymentMethodData::NetworkToken(_) => Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("CryptoPay"), - )), + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("CryptoPay"), + )) + } }?; Ok(cryptopay_request) } @@ -180,8 +184,8 @@ impl .map(|x| RedirectForm::from((x, common_utils::request::Method::Get))); Ok(PaymentsResponseData::TransactionResponse { resource_id: ResponseId::ConnectorTransactionId(item.response.data.id.clone()), - redirection_data, - mandate_reference: None, + redirection_data: Box::new(redirection_data), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: item diff --git a/crates/hyperswitch_connectors/src/connectors/ctp_mastercard.rs b/crates/hyperswitch_connectors/src/connectors/ctp_mastercard.rs new file mode 100644 index 000000000000..6a1c7e08815a --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/ctp_mastercard.rs @@ -0,0 +1,129 @@ +use common_utils::errors::CustomResult; +use error_stack::report; +use hyperswitch_domain_models::{ + router_data::{AccessToken, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + errors, webhooks, +}; + +use crate::constants::headers; + +#[derive(Clone)] +pub struct CtpMastercard; + +impl api::Payment for CtpMastercard {} +impl api::PaymentSession for CtpMastercard {} +impl api::ConnectorAccessToken for CtpMastercard {} +impl api::MandateSetup for CtpMastercard {} +impl api::PaymentAuthorize for CtpMastercard {} +impl api::PaymentSync for CtpMastercard {} +impl api::PaymentCapture for CtpMastercard {} +impl api::PaymentVoid for CtpMastercard {} +impl api::Refund for CtpMastercard {} +impl api::RefundExecute for CtpMastercard {} +impl api::RefundSync for CtpMastercard {} +impl api::PaymentToken for CtpMastercard {} + +impl ConnectorIntegration + for CtpMastercard +{ + // Not Implemented (R) +} + +impl ConnectorCommonExt for CtpMastercard +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } +} + +impl ConnectorCommon for CtpMastercard { + fn id(&self) -> &'static str { + "ctp_mastercard" + } + + fn base_url<'a>(&self, _connectors: &'a Connectors) -> &'a str { + "" + } +} + +impl ConnectorValidation for CtpMastercard {} + +impl ConnectorIntegration for CtpMastercard {} + +impl ConnectorIntegration for CtpMastercard {} + +impl ConnectorIntegration + for CtpMastercard +{ +} + +impl ConnectorIntegration + for CtpMastercard +{ +} + +impl ConnectorIntegration for CtpMastercard {} + +impl ConnectorIntegration for CtpMastercard {} + +impl ConnectorIntegration for CtpMastercard {} + +impl ConnectorIntegration for CtpMastercard {} + +impl ConnectorIntegration for CtpMastercard {} + +#[async_trait::async_trait] +impl webhooks::IncomingWebhook for CtpMastercard { + fn get_webhook_object_reference_id( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_event_type( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_resource_object( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } +} + +impl ConnectorSpecifications for CtpMastercard {} diff --git a/crates/router/src/connector/datatrans.rs b/crates/hyperswitch_connectors/src/connectors/datatrans.rs similarity index 69% rename from crates/router/src/connector/datatrans.rs rename to crates/hyperswitch_connectors/src/connectors/datatrans.rs index 9c7772c5b3ef..2a19a99c0fad 100644 --- a/crates/router/src/connector/datatrans.rs +++ b/crates/hyperswitch_connectors/src/connectors/datatrans.rs @@ -1,29 +1,52 @@ pub mod transformers; -use api_models::enums::{CaptureMethod, PaymentMethodType}; + +use api_models::webhooks::{IncomingWebhookEvent, ObjectReferenceId}; use base64::Engine; -use common_utils::types::{AmountConvertor, MinorUnit, MinorUnitForConnector}; +use common_enums::{CaptureMethod, PaymentMethod, PaymentMethodType}; +use common_utils::{ + consts::BASE64_ENGINE, + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, MinorUnit, MinorUnitForConnector}, +}; use error_stack::{report, ResultExt}; -use masking::PeekInterface; -use transformers::{self as datatrans}; - -use super::{utils as connector_utils, utils::RefundsRequestData}; -use crate::{ - configs::settings, - consts, - core::errors::{self, CustomResult}, - events::connector_api_logs::ConnectorEvent, - headers, - services::{ - self, - request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, +use hyperswitch_domain_models::{ + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, types::{ - self, - api::{self, ConnectorCommon, ConnectorCommonExt}, - ErrorResponse, RequestContent, Response, + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, }, - utils::BytesExt, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, +}; +use masking::{Mask, PeekInterface}; +use transformers as datatrans; + +use crate::{ + constants::headers, + types::ResponseRouterData, + utils::{convert_amount, RefundsRequestData}, }; impl api::Payment for Datatrans {} @@ -52,12 +75,8 @@ impl Datatrans { } } -impl - ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Datatrans +impl ConnectorIntegration + for Datatrans { // Not Implemented (R) } @@ -68,9 +87,9 @@ where { fn build_headers( &self, - req: &types::RouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let mut header = vec![( headers::CONTENT_TYPE.to_string(), self.get_content_type().to_string().into(), @@ -94,18 +113,18 @@ impl ConnectorCommon for Datatrans { "application/json" } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.datatrans.base_url.as_ref() } fn get_auth_header( &self, - auth_type: &types::ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { let auth = datatrans::DatatransAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; let auth_key = format!("{}:{}", auth.merchant_id.peek(), auth.passcode.peek()); - let auth_header = format!("Basic {}", consts::BASE64_ENGINE.encode(auth_key)); + let auth_header = format!("Basic {}", BASE64_ENGINE.encode(auth_key)); Ok(vec![( headers::AUTHORIZATION.to_string(), auth_header.into_masked(), @@ -137,14 +156,17 @@ impl ConnectorCommon for Datatrans { impl ConnectorValidation for Datatrans { //TODO: implement functions when support enabled - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - CaptureMethod::Automatic | CaptureMethod::Manual => Ok(()), + CaptureMethod::Automatic + | CaptureMethod::Manual + | CaptureMethod::SequentialAutomatic => Ok(()), CaptureMethod::ManualMultiple | CaptureMethod::Scheduled => { Err(errors::ConnectorError::NotSupported { message: capture_method.to_string(), @@ -156,34 +178,23 @@ impl ConnectorValidation for Datatrans { } } -impl ConnectorIntegration - for Datatrans -{ +impl ConnectorIntegration for Datatrans { //TODO: implement sessions flow } -impl ConnectorIntegration - for Datatrans -{ -} +impl ConnectorIntegration for Datatrans {} -impl - ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Datatrans +impl ConnectorIntegration + for Datatrans { } -impl ConnectorIntegration - for Datatrans -{ +impl ConnectorIntegration for Datatrans { fn get_headers( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -193,8 +204,8 @@ impl ConnectorIntegration CustomResult { let base_url = self.base_url(connectors); Ok(format!("{base_url}v1/transactions/authorize")) @@ -202,10 +213,10 @@ impl ConnectorIntegration CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_amount, req.request.currency, @@ -217,12 +228,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsAuthorizeType::get_url( self, req, connectors, )?) @@ -239,17 +250,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: datatrans::DatatransResponse = res .response .parse_struct("Datatrans PaymentsAuthorizeResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -265,14 +276,12 @@ impl ConnectorIntegration - for Datatrans -{ +impl ConnectorIntegration for Datatrans { fn get_headers( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -282,8 +291,8 @@ impl ConnectorIntegration CustomResult { let connector_payment_id = req .request @@ -296,12 +305,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) + RequestBuilder::new() + .method(Method::Get) .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) @@ -311,17 +320,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: datatrans::DatatransSyncResponse = res .response .parse_struct("datatrans DatatransSyncResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -337,14 +346,12 @@ impl ConnectorIntegration - for Datatrans -{ +impl ConnectorIntegration for Datatrans { fn get_headers( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -354,8 +361,8 @@ impl ConnectorIntegration CustomResult { let connector_payment_id = req.request.connector_transaction_id.clone(); let base_url = self.base_url(connectors); @@ -366,10 +373,10 @@ impl ConnectorIntegration CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_amount_to_capture, req.request.currency, @@ -381,12 +388,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsCaptureType::get_headers( @@ -401,10 +408,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response = if res.response.is_empty() { datatrans::DataTransCaptureResponse::Empty } else { @@ -414,7 +421,7 @@ impl ConnectorIntegration - for Datatrans -{ +impl ConnectorIntegration for Datatrans { fn get_headers( &self, - req: &types::PaymentsCancelRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -447,8 +452,8 @@ impl ConnectorIntegration CustomResult { let transaction_id = req.request.connector_transaction_id.clone(); let base_url = self.base_url(connectors); @@ -457,11 +462,11 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) @@ -474,10 +479,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response = if res.response.is_empty() { datatrans::DataTransCancelResponse::Empty } else { @@ -487,7 +492,7 @@ impl ConnectorIntegration - for Datatrans -{ +impl ConnectorIntegration for Datatrans { fn get_headers( &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -520,8 +523,8 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, + req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { let transaction_id = req.request.connector_transaction_id.clone(); let base_url = self.base_url(connectors); @@ -530,10 +533,10 @@ impl ConnectorIntegration, - _connectors: &settings::Connectors, + req: &RefundsRouterData, + _connectors: &Connectors, ) -> CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_refund_amount, req.request.currency, @@ -545,11 +548,11 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) .url(&types::RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundExecuteType::get_headers( @@ -564,10 +567,10 @@ impl ConnectorIntegration, + data: &RefundsRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult, errors::ConnectorError> { let response: datatrans::DatatransRefundsResponse = res .response .parse_struct("datatrans DatatransRefundsResponse") @@ -575,7 +578,7 @@ impl ConnectorIntegration - for Datatrans -{ +impl ConnectorIntegration for Datatrans { fn get_headers( &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -608,8 +609,8 @@ impl ConnectorIntegration CustomResult { let connector_refund_id = req.request.get_connector_refund_id()?; let base_url = self.base_url(connectors); @@ -618,12 +619,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) + RequestBuilder::new() + .method(Method::Get) .url(&types::RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundSyncType::get_headers(self, req, connectors)?) @@ -636,17 +637,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: datatrans::DatatransSyncResponse = res .response .parse_struct("datatrans DatatransSyncResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -663,25 +664,27 @@ impl ConnectorIntegration, - ) -> CustomResult { + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_event_type( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_resource_object( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for Datatrans {} diff --git a/crates/router/src/connector/datatrans/transformers.rs b/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs similarity index 74% rename from crates/router/src/connector/datatrans/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs index afc9288b6564..2ec76cb2937e 100644 --- a/crates/router/src/connector/datatrans/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs @@ -1,20 +1,24 @@ -use std::fmt::Debug; - +use common_enums::enums; use common_utils::types::MinorUnit; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::{PaymentsAuthorizeData, ResponseId}, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types, +}; +use hyperswitch_interfaces::errors; use masking::Secret; use serde::{Deserialize, Serialize}; use crate::{ - connector::{ - utils as connector_utils, - utils::{CardData, PaymentsAuthorizeRequestData}, - }, - core::errors, types::{ - self, - api::{self, enums}, - domain, - transformers::ForeignFrom, + PaymentsCancelResponseRouterData, PaymentsCaptureResponseRouterData, + PaymentsSyncResponseRouterData, RefundsResponseRouterData, ResponseRouterData, + }, + utils::{ + get_unimplemented_payment_method_error_message, CardData as _, PaymentsAuthorizeRequestData, }, }; @@ -159,7 +163,7 @@ impl TryFrom<&DatatransRouterData<&types::PaymentsAuthorizeRouterData>> item: &DatatransRouterData<&types::PaymentsAuthorizeRouterData>, ) -> Result { match item.router_data.request.payment_method_data.clone() { - domain::PaymentMethodData::Card(req_card) => Ok(Self { + PaymentMethodData::Card(req_card) => Ok(Self { amount: item.amount, currency: item.router_data.request.currency, card: PlainCardDetails { @@ -174,34 +178,36 @@ impl TryFrom<&DatatransRouterData<&types::PaymentsAuthorizeRouterData>> Some(enums::CaptureMethod::Automatic) ), }), - 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::RealTimePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::CardRedirect(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + PaymentMethodData::Wallet(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::CardRedirect(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( - connector_utils::get_unimplemented_payment_method_error_message("Datatrans"), + get_unimplemented_payment_method_error_message("Datatrans"), ))? } } } } -impl TryFrom<&types::ConnectorAuthType> for DatatransAuthType { +impl TryFrom<&ConnectorAuthType> for DatatransAuthType { type Error = error_stack::Report; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { + fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - types::ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { + ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { merchant_id: key1.clone(), passcode: api_key.clone(), }), @@ -210,20 +216,19 @@ impl TryFrom<&types::ConnectorAuthType> for DatatransAuthType { } } -impl ForeignFrom<(&DatatransResponse, bool)> for enums::AttemptStatus { - fn foreign_from((item, is_auto_capture): (&DatatransResponse, bool)) -> Self { - match item { - DatatransResponse::ErrorResponse(_) => Self::Failure, - DatatransResponse::TransactionResponse(_) => { - if is_auto_capture { - Self::Charged - } else { - Self::Authorized - } +fn get_status(item: &DatatransResponse, is_auto_capture: bool) -> enums::AttemptStatus { + match item { + DatatransResponse::ErrorResponse(_) => enums::AttemptStatus::Failure, + DatatransResponse::TransactionResponse(_) => { + if is_auto_capture { + enums::AttemptStatus::Charged + } else { + enums::AttemptStatus::Authorized } } } } + impl From for enums::AttemptStatus { fn from(item: SyncResponse) -> Self { match item.res_type { @@ -256,30 +261,16 @@ impl From for enums::RefundStatus { } impl - TryFrom< - types::ResponseRouterData< - F, - DatatransResponse, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, - >, - > for types::RouterData + TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< - F, - DatatransResponse, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, - >, + item: ResponseRouterData, ) -> Result { - let status = enums::AttemptStatus::foreign_from(( - &item.response, - item.data.request.is_auto_capture()?, - )); + let status = get_status(&item.response, item.data.request.is_auto_capture()?); let response = match &item.response { - DatatransResponse::ErrorResponse(error) => Err(types::ErrorResponse { + DatatransResponse::ErrorResponse(error) => Err(ErrorResponse { code: error.code.clone(), message: error.message.clone(), reason: Some(error.message.clone()), @@ -288,12 +279,12 @@ impl status_code: item.http_code, }), DatatransResponse::TransactionResponse(response) => { - Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( response.transaction_id.clone(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -323,16 +314,16 @@ impl TryFrom<&DatatransRouterData<&types::RefundsRouterData>> for Datatran } } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { match item.response { DatatransRefundsResponse::Error(error) => Ok(Self { - response: Err(types::ErrorResponse { + response: Err(ErrorResponse { code: error.code.clone(), message: error.message.clone(), reason: Some(error.message), @@ -343,7 +334,7 @@ impl TryFrom Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: response.transaction_id, refund_status: enums::RefundStatus::Success, }), @@ -353,15 +344,15 @@ impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { let response = match item.response { - DatatransSyncResponse::Error(error) => Err(types::ErrorResponse { + DatatransSyncResponse::Error(error) => Err(ErrorResponse { code: error.code.clone(), message: error.message.clone(), reason: Some(error.message), @@ -369,7 +360,7 @@ impl TryFrom connector_transaction_id: None, status_code: item.http_code, }), - DatatransSyncResponse::Response(response) => Ok(types::RefundsResponseData { + DatatransSyncResponse::Response(response) => Ok(RefundsResponseData { connector_refund_id: response.transaction_id.to_string(), refund_status: enums::RefundStatus::from(response), }), @@ -381,16 +372,16 @@ impl TryFrom } } -impl TryFrom> +impl TryFrom> for types::PaymentsSyncRouterData { type Error = error_stack::Report; fn try_from( - item: types::PaymentsSyncResponseRouterData, + item: PaymentsSyncResponseRouterData, ) -> Result { match item.response { DatatransSyncResponse::Error(error) => { - let response = Err(types::ErrorResponse { + let response = Err(ErrorResponse { code: error.code.clone(), message: error.message.clone(), reason: Some(error.message), @@ -405,13 +396,13 @@ impl TryFrom> } DatatransSyncResponse::Response(response) => { let resource_id = - types::ResponseId::ConnectorTransactionId(response.transaction_id.to_string()); + ResponseId::ConnectorTransactionId(response.transaction_id.to_string()); Ok(Self { status: enums::AttemptStatus::from(response), - response: Ok(types::PaymentsResponseData::TransactionResponse { + response: Ok(PaymentsResponseData::TransactionResponse { resource_id, - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -440,13 +431,13 @@ impl TryFrom<&DatatransRouterData<&types::PaymentsCaptureRouterData>> } } -impl TryFrom> +impl TryFrom> for types::PaymentsCaptureRouterData { type Error = error_stack::Report; fn try_from( - item: types::PaymentsCaptureResponseRouterData, + item: PaymentsCaptureResponseRouterData, ) -> Result { let status = match item.response { DataTransCaptureResponse::Error(error) => { @@ -473,13 +464,13 @@ impl TryFrom> } } -impl TryFrom> +impl TryFrom> for types::PaymentsCancelRouterData { type Error = error_stack::Report; fn try_from( - item: types::PaymentsCancelResponseRouterData, + item: PaymentsCancelResponseRouterData, ) -> Result { let status = match item.response { // Datatrans http code 204 implies Successful Cancellation diff --git a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs index a861155afb3a..3098d27e7ecb 100644 --- a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs +++ b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs @@ -27,7 +27,10 @@ use hyperswitch_domain_models::{ PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, RefundsData, SetupMandateRequestData, }, - router_response_types::{PaymentsResponseData, RefundsResponseData}, + router_response_types::{ + ConnectorInfo, PaymentMethodDetails, PaymentsResponseData, RefundsResponseData, + SupportedPaymentMethods, SupportedPaymentMethodsExt, + }, types::{ PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, PaymentsCompleteAuthorizeRouterData, PaymentsSyncRouterData, RefreshTokenRouterData, @@ -35,13 +38,17 @@ use hyperswitch_domain_models::{ }, }; use hyperswitch_interfaces::{ - api::{self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorValidation}, + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, configs::Connectors, errors, events::connector_api_logs::ConnectorEvent, types::{self, Response}, webhooks, }; +use lazy_static::lazy_static; use masking::{ExposeInterface, Mask, Secret}; use rand::distributions::{Alphanumeric, DistString}; use ring::hmac; @@ -172,20 +179,6 @@ impl ConnectorCommon for Deutschebank { } impl ConnectorValidation for Deutschebank { - fn validate_capture_method( - &self, - capture_method: Option, - _pmt: Option, - ) -> CustomResult<(), errors::ConnectorError> { - let capture_method = capture_method.unwrap_or_default(); - match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), - enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( - utils::construct_not_implemented_error_report(capture_method, self.id()), - ), - } - } - fn validate_mandate_payment( &self, pm_type: Option, @@ -946,3 +939,50 @@ impl webhooks::IncomingWebhook for Deutschebank { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +lazy_static! { + static ref DEUTSCHEBANK_SUPPORTED_PAYMENT_METHODS: SupportedPaymentMethods = { + let supported_capture_methods = vec![ + enums::CaptureMethod::Automatic, + enums::CaptureMethod::Manual, + enums::CaptureMethod::SequentialAutomatic, + ]; + let mut deutschebank_supported_payment_methods = SupportedPaymentMethods::new(); + + deutschebank_supported_payment_methods.add( + enums::PaymentMethod::BankDebit, + enums::PaymentMethodType::Sepa, + PaymentMethodDetails{ + mandates: enums::FeatureStatus::NotSupported, + refunds: enums::FeatureStatus::NotSupported, + supported_capture_methods, + } + ); + + deutschebank_supported_payment_methods + }; + + static ref DEUTSCHEBANK_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo { + description: + "Deutsche Bank is a German multinational investment bank and financial services company " + .to_string(), + connector_type: enums::PaymentConnectorCategory::BankAcquirer, + }; + + static ref DEUTSCHEBANK_SUPPORTED_WEBHOOK_FLOWS: Vec = Vec::new(); + +} + +impl ConnectorSpecifications for Deutschebank { + fn get_connector_about(&self) -> Option<&'static ConnectorInfo> { + Some(&*DEUTSCHEBANK_CONNECTOR_INFO) + } + + fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> { + Some(&*DEUTSCHEBANK_SUPPORTED_PAYMENT_METHODS) + } + + fn get_supported_webhook_flows(&self) -> Option<&'static [enums::EventClass]> { + Some(&*DEUTSCHEBANK_SUPPORTED_WEBHOOK_FLOWS) + } +} diff --git a/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs b/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs index 30b1d65134a3..85c5fc8dc816 100644 --- a/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs @@ -5,7 +5,7 @@ use common_utils::{ext_traits::ValueExt, pii::Email, types::MinorUnit}; use error_stack::ResultExt; use hyperswitch_domain_models::{ payment_method_data::{BankDebitData, PaymentMethodData}, - router_data::{AccessToken, ConnectorAuthType, RouterData}, + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::{ payments::{Authorize, Capture, CompleteAuthorize, PSync}, refunds::{Execute, RSync}, @@ -146,7 +146,9 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>> .and_then(|mandate_id| mandate_id.mandate_reference_id) { None => { - if item.router_data.request.is_mandate_payment() { + // To facilitate one-off payments via SEPA with Deutsche Bank, we are considering not storing the connector mandate ID in our system if future usage is on-session. + // We will only check for customer acceptance to make a one-off payment. we will be storing the connector mandate details only when setup future usage is off-session. + if item.router_data.request.customer_acceptance.is_some() { match item.router_data.request.payment_method_data.clone() { PaymentMethodData::BankDebit(BankDebitData::SepaBankDebit { iban, .. @@ -167,14 +169,14 @@ impl TryFrom<&DeutschebankRouterData<&PaymentsAuthorizeRouterData>> } } else { Err(errors::ConnectorError::MissingRequiredField { - field_name: "setup_future_usage or customer_acceptance.acceptance_type", + field_name: "customer_acceptance", } .into()) } } Some(api_models::payments::MandateReferenceId::ConnectorMandateId(mandate_data)) => { let mandate_metadata: DeutschebankMandateMetadata = mandate_data - .mandate_metadata + .get_mandate_metadata() .ok_or(errors::ConnectorError::MissingConnectorMandateMetadata)? .clone() .parse_value("DeutschebankMandateMetadata") @@ -261,6 +263,21 @@ pub struct DeutschebankMandatePostResponse { state: Option, } +fn get_error_response(error_code: String, error_reason: String, status_code: u16) -> ErrorResponse { + ErrorResponse { + code: error_code.to_string(), + message: error_reason.clone(), + reason: Some(error_reason), + status_code, + attempt_status: None, + connector_transaction_id: None, + } +} + +fn is_response_success(rc: &String) -> bool { + rc == "0" +} + impl TryFrom< ResponseRouterData< @@ -284,45 +301,52 @@ impl Some(date) => date.chars().take(10).collect(), None => time::OffsetDateTime::now_utc().date().to_string(), }; - match item.response.reference.clone() { - Some(reference) => Ok(Self { - status: if item.response.rc == "0" { - match item.response.state.clone() { - Some(state) => common_enums::AttemptStatus::from(state), - None => common_enums::AttemptStatus::Failure, - } - } else { - common_enums::AttemptStatus::Failure - }, + let response_code = item.response.rc.clone(); + let is_response_success = is_response_success(&response_code); + + match ( + item.response.reference.clone(), + item.response.state.clone(), + is_response_success, + ) { + (Some(reference), Some(state), true) => Ok(Self { + status: common_enums::AttemptStatus::from(state), response: Ok(PaymentsResponseData::TransactionResponse { resource_id: ResponseId::NoResponseId, - redirection_data: Some(RedirectForm::Form { + redirection_data: Box::new(Some(RedirectForm::Form { endpoint: item.data.request.get_complete_authorize_url()?, method: common_utils::request::Method::Get, form_fields: HashMap::from([ ("reference".to_string(), reference.clone()), ("signed_on".to_string(), signed_on.clone()), ]), - }), - mandate_reference: Some(MandateReference { - connector_mandate_id: item.response.mandate_id, - payment_method_id: None, - mandate_metadata: Some(serde_json::json!(DeutschebankMandateMetadata { - account_holder: item.data.get_billing_address()?.get_full_name()?, - iban: match item.data.request.payment_method_data.clone() { - PaymentMethodData::BankDebit(BankDebitData::SepaBankDebit { - iban, - .. - }) => Ok(Secret::from(iban.peek().replace(" ", ""))), - _ => Err(errors::ConnectorError::MissingRequiredField { - field_name: - "payment_method_data.bank_debit.sepa_bank_debit.iban" - }), - }?, - reference: Secret::from(reference), - signed_on, - })), - }), + })), + mandate_reference: if item.data.request.is_mandate_payment() { + Box::new(Some(MandateReference { + connector_mandate_id: item.response.mandate_id, + payment_method_id: None, + mandate_metadata: Some(Secret::new( + serde_json::json!(DeutschebankMandateMetadata { + account_holder: item.data.get_billing_address()?.get_full_name()?, + iban: match item.data.request.payment_method_data.clone() { + PaymentMethodData::BankDebit(BankDebitData::SepaBankDebit { + iban, + .. + }) => Ok(Secret::from(iban.peek().replace(" ", ""))), + _ => Err(errors::ConnectorError::MissingRequiredField { + field_name: + "payment_method_data.bank_debit.sepa_bank_debit.iban" + }), + }?, + reference: Secret::from(reference.clone()), + signed_on, + }), + )), + connector_mandate_request_reference_id: None, + })) + } else { + Box::new(None) + }, connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -331,8 +355,13 @@ impl }), ..item.data }), - None => Ok(Self { + _ => Ok(Self { status: common_enums::AttemptStatus::Failure, + response: Err(get_error_response( + response_code.clone(), + item.response.message.clone(), + item.http_code, + )), ..item.data }), } @@ -358,27 +387,36 @@ impl PaymentsResponseData, >, ) -> Result { - Ok(Self { - status: if item.response.rc == "0" { - match item.data.request.is_auto_capture()? { + let response_code = item.response.rc.clone(); + if is_response_success(&response_code) { + Ok(Self { + status: match item.data.request.is_auto_capture()? { true => common_enums::AttemptStatus::Charged, false => common_enums::AttemptStatus::Authorized, - } - } else { - common_enums::AttemptStatus::Failure - }, - response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(item.response.tx_id), - redirection_data: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: None, - incremental_authorization_allowed: None, - charge_id: None, - }), - ..item.data - }) + }, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.tx_id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } else { + Ok(Self { + status: common_enums::AttemptStatus::Failure, + response: Err(get_error_response( + response_code.clone(), + item.response.message.clone(), + item.http_code, + )), + ..item.data + }) + } } } @@ -561,27 +599,36 @@ impl PaymentsResponseData, >, ) -> Result { - Ok(Self { - status: if item.response.rc == "0" { - match item.data.request.is_auto_capture()? { + let response_code = item.response.rc.clone(); + if is_response_success(&response_code) { + Ok(Self { + status: match item.data.request.is_auto_capture()? { true => common_enums::AttemptStatus::Charged, false => common_enums::AttemptStatus::Authorized, - } - } else { - common_enums::AttemptStatus::Failure - }, - response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(item.response.tx_id), - redirection_data: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: None, - incremental_authorization_allowed: None, - charge_id: None, - }), - ..item.data - }) + }, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.tx_id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } else { + Ok(Self { + status: common_enums::AttemptStatus::Failure, + response: Err(get_error_response( + response_code.clone(), + item.response.message.clone(), + item.http_code, + )), + ..item.data + }) + } } } @@ -628,24 +675,33 @@ impl PaymentsResponseData, >, ) -> Result { - Ok(Self { - response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(item.response.tx_id), - redirection_data: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: None, - incremental_authorization_allowed: None, - charge_id: None, - }), - status: if item.response.rc == "0" { - common_enums::AttemptStatus::Charged - } else { - common_enums::AttemptStatus::Failure - }, - ..item.data - }) + let response_code = item.response.rc.clone(); + if is_response_success(&response_code) { + Ok(Self { + status: common_enums::AttemptStatus::Charged, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.tx_id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } else { + Ok(Self { + status: common_enums::AttemptStatus::Failure, + response: Err(get_error_response( + response_code.clone(), + item.response.message.clone(), + item.http_code, + )), + ..item.data + }) + } } } @@ -668,7 +724,8 @@ impl PaymentsResponseData, >, ) -> Result { - let status = if item.response.rc == "0" { + let response_code = item.response.rc.clone(); + let status = if is_response_success(&response_code) { item.response .tx_action .and_then(|tx_action| match tx_action { @@ -690,6 +747,15 @@ impl Some(common_enums::AttemptStatus::Failure) }; match status { + Some(common_enums::AttemptStatus::Failure) => Ok(Self { + status: common_enums::AttemptStatus::Failure, + response: Err(get_error_response( + response_code.clone(), + item.response.message.clone(), + item.http_code, + )), + ..item.data + }), Some(status) => Ok(Self { status, ..item.data @@ -720,14 +786,23 @@ impl TryFrom> fn try_from( item: PaymentsCancelResponseRouterData, ) -> Result { - Ok(Self { - status: if item.response.rc == "0" { - common_enums::AttemptStatus::Voided - } else { - common_enums::AttemptStatus::VoidFailed - }, - ..item.data - }) + let response_code = item.response.rc.clone(); + if is_response_success(&response_code) { + Ok(Self { + status: common_enums::AttemptStatus::Voided, + ..item.data + }) + } else { + Ok(Self { + status: common_enums::AttemptStatus::VoidFailed, + response: Err(get_error_response( + response_code.clone(), + item.response.message.clone(), + item.http_code, + )), + ..item.data + }) + } } } @@ -754,17 +829,26 @@ impl TryFrom> fn try_from( item: RefundsResponseRouterData, ) -> Result { - Ok(Self { - response: Ok(RefundsResponseData { - connector_refund_id: item.response.tx_id, - refund_status: if item.response.rc == "0" { - enums::RefundStatus::Success - } else { - enums::RefundStatus::Failure - }, - }), - ..item.data - }) + let response_code = item.response.rc.clone(); + if is_response_success(&response_code) { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.tx_id, + refund_status: enums::RefundStatus::Success, + }), + ..item.data + }) + } else { + Ok(Self { + status: common_enums::AttemptStatus::Failure, + response: Err(get_error_response( + response_code.clone(), + item.response.message.clone(), + item.http_code, + )), + ..item.data + }) + } } } @@ -775,7 +859,8 @@ impl TryFrom> fn try_from( item: RefundsResponseRouterData, ) -> Result { - let status = if item.response.rc == "0" { + let response_code = item.response.rc.clone(); + let status = if is_response_success(&response_code) { item.response .tx_action .and_then(|tx_action| match tx_action { @@ -794,7 +879,17 @@ impl TryFrom> } else { Some(enums::RefundStatus::Failure) }; + match status { + Some(enums::RefundStatus::Failure) => Ok(Self { + status: common_enums::AttemptStatus::Failure, + response: Err(get_error_response( + response_code.clone(), + item.response.message.clone(), + item.http_code, + )), + ..item.data + }), Some(refund_status) => Ok(Self { response: Ok(RefundsResponseData { refund_status, diff --git a/crates/hyperswitch_connectors/src/connectors/digitalvirgo.rs b/crates/hyperswitch_connectors/src/connectors/digitalvirgo.rs new file mode 100644 index 000000000000..74d76c4754ff --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/digitalvirgo.rs @@ -0,0 +1,543 @@ +pub mod transformers; + +use base64::Engine; +use common_enums::enums; +use common_utils::{ + consts, + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, FloatMajorUnit, FloatMajorUnitForConnector}, +}; +use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::{ + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{ + Authorize, Capture, CompleteAuthorize, PSync, PaymentMethodToken, Session, + SetupMandate, Void, + }, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, CompleteAuthorizeData, PaymentMethodTokenizationData, + PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, + PaymentsSyncData, RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, + PaymentsCompleteAuthorizeRouterData, PaymentsSyncRouterData, RefundSyncRouterData, + RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorRedirectResponse, + ConnectorSpecifications, ConnectorValidation, + }, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks, +}; +use masking::{Mask, PeekInterface}; +use transformers as digitalvirgo; + +use crate::{constants::headers, types::ResponseRouterData, utils}; + +#[derive(Clone)] +pub struct Digitalvirgo { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl Digitalvirgo { + pub fn new() -> &'static Self { + &Self { + amount_converter: &FloatMajorUnitForConnector, + } + } +} + +impl api::Payment for Digitalvirgo {} +impl api::PaymentSession for Digitalvirgo {} +impl api::ConnectorAccessToken for Digitalvirgo {} +impl api::MandateSetup for Digitalvirgo {} +impl api::PaymentAuthorize for Digitalvirgo {} +impl api::PaymentSync for Digitalvirgo {} +impl api::PaymentCapture for Digitalvirgo {} +impl api::PaymentVoid for Digitalvirgo {} +impl api::Refund for Digitalvirgo {} +impl api::RefundExecute for Digitalvirgo {} +impl api::RefundSync for Digitalvirgo {} +impl api::PaymentToken for Digitalvirgo {} +impl api::PaymentsCompleteAuthorize for Digitalvirgo {} + +impl ConnectorIntegration + for Digitalvirgo +{ +} + +impl ConnectorCommonExt for Digitalvirgo +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } +} + +impl ConnectorCommon for Digitalvirgo { + fn id(&self) -> &'static str { + "digitalvirgo" + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Base + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { + connectors.digitalvirgo.base_url.as_ref() + } + + fn get_auth_header( + &self, + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + let auth = digitalvirgo::DigitalvirgoAuthType::try_from(auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + let encoded_api_key = consts::BASE64_ENGINE.encode(format!( + "{}:{}", + auth.username.peek(), + auth.password.peek() + )); + Ok(vec![( + headers::AUTHORIZATION.to_string(), + format!("Basic {encoded_api_key}").into_masked(), + )]) + } + + fn build_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: digitalvirgo::DigitalvirgoErrorResponse = res + .response + .parse_struct("DigitalvirgoErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + let error_code = response.cause.or(response.operation_error); + + Ok(ErrorResponse { + status_code: res.status_code, + code: error_code + .clone() + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_CODE.to_string()), + message: error_code + .unwrap_or(hyperswitch_interfaces::consts::NO_ERROR_MESSAGE.to_string()), + reason: response.description, + attempt_status: None, + connector_transaction_id: None, + }) + } +} + +impl ConnectorValidation for Digitalvirgo { + fn validate_connector_against_payment_request( + &self, + capture_method: Option, + _payment_method: enums::PaymentMethod, + _pmt: Option, + ) -> CustomResult<(), errors::ConnectorError> { + let capture_method = capture_method.unwrap_or_default(); + match capture_method { + enums::CaptureMethod::Automatic | enums::CaptureMethod::SequentialAutomatic => Ok(()), + enums::CaptureMethod::Manual + | enums::CaptureMethod::ManualMultiple + | enums::CaptureMethod::Scheduled => Err(utils::construct_not_supported_error_report( + capture_method, + self.id(), + )), + } + } + + fn validate_psync_reference_id( + &self, + _data: &PaymentsSyncData, + _is_three_ds: bool, + _status: enums::AttemptStatus, + _connector_meta_data: Option, + ) -> CustomResult<(), errors::ConnectorError> { + // in case we dont have transaction id, we can make psync using attempt id + Ok(()) + } +} + +impl ConnectorRedirectResponse for Digitalvirgo { + fn get_flow_type( + &self, + _query_params: &str, + _json_payload: Option, + action: enums::PaymentAction, + ) -> CustomResult { + match action { + enums::PaymentAction::PSync + | enums::PaymentAction::CompleteAuthorize + | enums::PaymentAction::PaymentAuthenticateCompleteAuthorize => { + Ok(enums::CallConnectorAction::Trigger) + } + } + } +} + +impl ConnectorIntegration for Digitalvirgo {} + +impl ConnectorIntegration for Digitalvirgo {} + +impl ConnectorIntegration + for Digitalvirgo +{ +} + +impl ConnectorIntegration for Digitalvirgo { + fn get_headers( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!("{}/payment", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + + let surcharge_amount = req.request.surcharge_details.clone().and_then(|surcharge| { + utils::convert_amount( + self.amount_converter, + surcharge.surcharge_amount, + req.request.currency, + ) + .ok() + }); + + let connector_router_data = + digitalvirgo::DigitalvirgoRouterData::from((amount, surcharge_amount, req)); + let connector_req = + digitalvirgo::DigitalvirgoPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: digitalvirgo::DigitalvirgoPaymentsResponse = res + .response + .parse_struct("Digitalvirgo PaymentsAuthorizeResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Digitalvirgo { + fn get_headers( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!( + "{}{}{}", + self.base_url(connectors), + "/payment/state?partnerTransactionId=", + req.connector_request_reference_id + )) + } + + fn build_request( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response_details: Vec = res + .response + .parse_struct("digitalvirgo PaymentsSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + let response = response_details + .first() + .cloned() + .ok_or(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration + for Digitalvirgo +{ + fn get_headers( + &self, + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + fn get_url( + &self, + _req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!( + "{}{}", + self.base_url(connectors), + "/payment/confirmation" + )) + } + fn get_request_body( + &self, + req: &PaymentsCompleteAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = digitalvirgo::DigitalvirgoConfirmRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + fn build_request( + &self, + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsCompleteAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsCompleteAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCompleteAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: digitalvirgo::DigitalvirgoPaymentsResponse = res + .response + .parse_struct("DigitalvirgoPaymentsResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Digitalvirgo { + fn build_request( + &self, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::FlowNotSupported { + flow: "capture".to_string(), + connector: "digitalvirgo".to_string(), + } + .into()) + } +} + +impl ConnectorIntegration for Digitalvirgo {} + +impl ConnectorIntegration for Digitalvirgo { + fn build_request( + &self, + _req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::FlowNotSupported { + flow: "refund".to_string(), + connector: "digitalvirgo".to_string(), + } + .into()) + } +} + +impl ConnectorIntegration for Digitalvirgo { + fn build_request( + &self, + _req: &RefundSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::FlowNotSupported { + flow: "refund_sync".to_string(), + connector: "digitalvirgo".to_string(), + } + .into()) + } +} + +#[async_trait::async_trait] +impl webhooks::IncomingWebhook for Digitalvirgo { + fn get_webhook_object_reference_id( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_event_type( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_resource_object( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } +} + +impl ConnectorSpecifications for Digitalvirgo {} diff --git a/crates/hyperswitch_connectors/src/connectors/digitalvirgo/transformers.rs b/crates/hyperswitch_connectors/src/connectors/digitalvirgo/transformers.rs new file mode 100644 index 000000000000..a9408e4e1c52 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/digitalvirgo/transformers.rs @@ -0,0 +1,341 @@ +use common_enums::enums; +use common_utils::types::FloatMajorUnit; +use error_stack::ResultExt; +use hyperswitch_domain_models::{ + payment_method_data::{MobilePaymentData, PaymentMethodData}, + router_data::{ConnectorAuthType, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{PaymentsAuthorizeRouterData, PaymentsCompleteAuthorizeRouterData, RefundsRouterData}, +}; +use hyperswitch_interfaces::errors; +use masking::{ExposeInterface, Secret}; +use serde::{Deserialize, Serialize}; + +use crate::{ + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::{PaymentsAuthorizeRequestData, PaymentsCompleteAuthorizeRequestData}, +}; + +pub struct DigitalvirgoRouterData { + pub amount: FloatMajorUnit, + pub surcharge_amount: Option, + pub router_data: T, +} + +impl From<(FloatMajorUnit, Option, T)> for DigitalvirgoRouterData { + fn from((amount, surcharge_amount, item): (FloatMajorUnit, Option, T)) -> Self { + Self { + amount, + surcharge_amount, + router_data: item, + } + } +} + +#[derive(Default, Debug, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct DigitalvirgoPaymentsRequest { + amount: FloatMajorUnit, + amount_surcharge: Option, + client_uid: Option, + msisdn: String, + product_name: String, + description: Option, + partner_transaction_id: String, +} + +impl TryFrom<&DigitalvirgoRouterData<&PaymentsAuthorizeRouterData>> + for DigitalvirgoPaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &DigitalvirgoRouterData<&PaymentsAuthorizeRouterData>, + ) -> Result { + match item.router_data.request.payment_method_data.clone() { + PaymentMethodData::MobilePayment(mobile_payment_data) => match mobile_payment_data { + MobilePaymentData::DirectCarrierBilling { msisdn, client_uid } => { + let order_details = item.router_data.request.get_order_details()?; + let product_name = order_details + .first() + .map(|order| order.product_name.to_owned()) + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "product_name", + })?; + + Ok(Self { + amount: item.amount.to_owned(), + amount_surcharge: item.surcharge_amount.to_owned(), + client_uid, + msisdn, + product_name, + description: item.router_data.description.to_owned(), + partner_transaction_id: item + .router_data + .connector_request_reference_id + .to_owned(), + }) + } + }, + _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), + } + } +} + +pub struct DigitalvirgoAuthType { + pub(super) username: Secret, + pub(super) password: Secret, +} + +impl TryFrom<&ConnectorAuthType> for DigitalvirgoAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &ConnectorAuthType) -> Result { + match auth_type { + ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { + username: key1.to_owned(), + password: api_key.to_owned(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum DigitalvirgoPaymentStatus { + Ok, + ConfirmPayment, +} + +impl From for common_enums::AttemptStatus { + fn from(item: DigitalvirgoPaymentStatus) -> Self { + match item { + DigitalvirgoPaymentStatus::Ok => Self::Charged, + DigitalvirgoPaymentStatus::ConfirmPayment => Self::AuthenticationPending, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DigitalvirgoPaymentsResponse { + state: DigitalvirgoPaymentStatus, + transaction_id: String, + consent: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DigitalvirgoConsentStatus { + required: Option, +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + // show if consent is required in next action + let connector_metadata = item + .response + .consent + .and_then(|consent_status| { + consent_status.required.map(|consent_required| { + if consent_required { + serde_json::json!({ + "consent_data_required": "consent_required", + }) + } else { + serde_json::json!({ + "consent_data_required": "consent_not_required", + }) + } + }) + }) + .or(Some(serde_json::json!({ + "consent_data_required": "consent_not_required", + }))); + Ok(Self { + status: common_enums::AttemptStatus::from(item.response.state), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.transaction_id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum DigitalvirgoPaymentSyncStatus { + Accepted, + Payed, + Pending, + Cancelled, + Rejected, + Locked, +} + +impl From for common_enums::AttemptStatus { + fn from(item: DigitalvirgoPaymentSyncStatus) -> Self { + match item { + DigitalvirgoPaymentSyncStatus::Accepted => Self::AuthenticationPending, + DigitalvirgoPaymentSyncStatus::Payed => Self::Charged, + DigitalvirgoPaymentSyncStatus::Pending | DigitalvirgoPaymentSyncStatus::Locked => { + Self::Pending + } + DigitalvirgoPaymentSyncStatus::Cancelled => Self::Voided, + DigitalvirgoPaymentSyncStatus::Rejected => Self::Failure, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DigitalvirgoPaymentSyncResponse { + payment_status: DigitalvirgoPaymentSyncStatus, + transaction_id: String, +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + Ok(Self { + status: common_enums::AttemptStatus::from(item.response.payment_status), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.transaction_id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } +} + +#[derive(Default, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DigitalvirgoConfirmRequest { + transaction_id: String, + token: Secret, +} + +#[derive(Default, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DigitalvirgoRedirectResponseData { + otp: Secret, +} + +impl TryFrom<&PaymentsCompleteAuthorizeRouterData> for DigitalvirgoConfirmRequest { + type Error = error_stack::Report; + fn try_from(item: &PaymentsCompleteAuthorizeRouterData) -> Result { + let payload_data = item.request.get_redirect_response_payload()?.expose(); + + let otp_data: DigitalvirgoRedirectResponseData = serde_json::from_value(payload_data) + .change_context(errors::ConnectorError::MissingConnectorRedirectionPayload { + field_name: "otp for transaction", + })?; + + Ok(Self { + transaction_id: item + .request + .connector_transaction_id + .clone() + .ok_or(errors::ConnectorError::MissingConnectorTransactionID)?, + token: otp_data.otp, + }) + } +} + +#[derive(Default, Debug, Serialize)] +pub struct DigitalvirgoRefundRequest { + pub amount: FloatMajorUnit, +} + +impl TryFrom<&DigitalvirgoRouterData<&RefundsRouterData>> for DigitalvirgoRefundRequest { + type Error = error_stack::Report; + fn try_from(item: &DigitalvirgoRouterData<&RefundsRouterData>) -> Result { + Ok(Self { + amount: item.amount.to_owned(), + }) + } +} + +#[allow(dead_code)] +#[derive(Debug, Serialize, Default, Deserialize, Clone)] +pub enum RefundStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for enums::RefundStatus { + fn from(item: RefundStatus) -> Self { + match item { + RefundStatus::Succeeded => Self::Success, + RefundStatus::Failed => Self::Failure, + RefundStatus::Processing => Self::Pending, + } + } +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct RefundResponse { + id: String, + status: RefundStatus, +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct DigitalvirgoErrorResponse { + pub cause: Option, + pub operation_error: Option, + pub description: Option, +} diff --git a/crates/hyperswitch_connectors/src/connectors/dlocal.rs b/crates/hyperswitch_connectors/src/connectors/dlocal.rs index 78d3ad4f63e9..0aefd65e4a0f 100644 --- a/crates/hyperswitch_connectors/src/connectors/dlocal.rs +++ b/crates/hyperswitch_connectors/src/connectors/dlocal.rs @@ -32,7 +32,10 @@ use hyperswitch_domain_models::{ }, }; use hyperswitch_interfaces::{ - api::{self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorValidation}, + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, configs::Connectors, errors, events::connector_api_logs::ConnectorEvent, @@ -154,14 +157,17 @@ impl ConnectorCommon for Dlocal { } impl ConnectorValidation for Dlocal { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( utils::construct_not_supported_error_report(capture_method, self.id()), ), @@ -686,3 +692,5 @@ impl webhooks::IncomingWebhook for Dlocal { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for Dlocal {} diff --git a/crates/hyperswitch_connectors/src/connectors/dlocal/transformers.rs b/crates/hyperswitch_connectors/src/connectors/dlocal/transformers.rs index 385601294b35..c5b4b940c101 100644 --- a/crates/hyperswitch_connectors/src/connectors/dlocal/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/dlocal/transformers.rs @@ -1,4 +1,3 @@ -use api_models::payments::AddressDetails; use common_enums::enums; use common_utils::{pii::Email, request::Method}; use error_stack::ResultExt; @@ -107,6 +106,7 @@ impl TryFrom<&DlocalRouterData<&types::PaymentsAuthorizeRouterData>> for DlocalP let should_capture = matches!( item.router_data.request.capture_method, Some(enums::CaptureMethod::Automatic) + | Some(enums::CaptureMethod::SequentialAutomatic) ); let payment_request = Self { amount: item.amount, @@ -166,19 +166,25 @@ impl TryFrom<&DlocalRouterData<&types::PaymentsAuthorizeRouterData>> for DlocalP | PaymentMethodData::MandatePayment | PaymentMethodData::Reward | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) | PaymentMethodData::Upi(_) | PaymentMethodData::Voucher(_) | PaymentMethodData::GiftCard(_) | PaymentMethodData::OpenBanking(_) | PaymentMethodData::CardToken(_) - | PaymentMethodData::NetworkToken(_) => Err(errors::ConnectorError::NotImplemented( - crate::utils::get_unimplemented_payment_method_error_message("Dlocal"), - ))?, + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + Err(errors::ConnectorError::NotImplemented( + crate::utils::get_unimplemented_payment_method_error_message("Dlocal"), + ))? + } } } } -fn get_payer_name(address: &AddressDetails) -> Option> { +fn get_payer_name( + address: &hyperswitch_domain_models::address::AddressDetails, +) -> Option> { let first_name = address .first_name .clone() @@ -323,8 +329,8 @@ impl TryFrom TryFrom TryFrom TryFrom( + item: &T, +) -> Result>, errors::ConnectorError> { + let xml_content = quick_xml::se::to_string_with_root("txn", &item).map_err(|e| { + router_env::logger::error!("Error serializing Struct: {:?}", e); + errors::ConnectorError::ResponseDeserializationFailed + })?; + + let mut result = HashMap::new(); + result.insert( + "xmldata".to_string(), + Secret::<_, WithoutType>::new(xml_content), + ); + Ok(result) +} +#[derive(Clone)] +pub struct Elavon { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl Elavon { + pub fn new() -> &'static Self { + &Self { + amount_converter: &StringMajorUnitForConnector, + } + } +} + +impl api::Payment for Elavon {} +impl api::PaymentSession for Elavon {} +impl api::ConnectorAccessToken for Elavon {} +impl api::MandateSetup for Elavon {} +impl api::PaymentAuthorize for Elavon {} +impl api::PaymentSync for Elavon {} +impl api::PaymentCapture for Elavon {} +impl api::PaymentVoid for Elavon {} +impl api::Refund for Elavon {} +impl api::RefundExecute for Elavon {} +impl api::RefundSync for Elavon {} +impl api::PaymentToken for Elavon {} + +impl ConnectorIntegration + for Elavon +{ + // Not Implemented (R) +} + +impl ConnectorCommonExt for Elavon +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } +} + +impl ConnectorCommon for Elavon { + fn id(&self) -> &'static str { + "elavon" + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Base + } + + fn common_get_content_type(&self) -> &'static str { + "application/x-www-form-urlencoded" + } + + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { + connectors.elavon.base_url.as_ref() + } +} + +impl ConnectorIntegration for Elavon {} + +impl ConnectorIntegration for Elavon {} + +impl ConnectorIntegration for Elavon {} + +impl ConnectorIntegration for Elavon { + fn get_headers( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!("{}processxml.do", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + + let connector_router_data = elavon::ElavonRouterData::from((amount, req)); + let connector_req = elavon::ElavonPaymentsRequest::try_from(&connector_router_data)?; + router_env::logger::info!(raw_connector_request=?connector_req); + Ok(RequestContent::FormUrlEncoded(Box::new(struct_to_xml( + &connector_req, + )?))) + } + + fn build_request( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: elavon::ElavonPaymentsResponse = + utils::deserialize_xml_to_struct(&res.response)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Elavon { + fn get_headers( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!("{}processxml.do", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &PaymentsSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = elavon::SyncRequest::try_from(req)?; + router_env::logger::info!(raw_connector_request=?connector_req); + Ok(RequestContent::FormUrlEncoded(Box::new(struct_to_xml( + &connector_req, + )?))) + } + + fn build_request( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .set_body(types::PaymentsSyncType::get_request_body( + self, req, connectors, + )?) + .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: elavon::ElavonSyncResponse = utils::deserialize_xml_to_struct(&res.response)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Elavon { + fn get_headers( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!("{}processxml.do", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount_to_capture, + req.request.currency, + )?; + let connector_router_data = elavon::ElavonRouterData::from((amount, req)); + let connector_req = elavon::PaymentsCaptureRequest::try_from(&connector_router_data)?; + router_env::logger::info!(raw_connector_request=?connector_req); + Ok(RequestContent::FormUrlEncoded(Box::new(struct_to_xml( + &connector_req, + )?))) + } + + fn build_request( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsCaptureType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCaptureType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCaptureRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: elavon::ElavonPaymentsResponse = + utils::deserialize_xml_to_struct(&res.response)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Elavon { + fn build_request( + &self, + _req: &PaymentsCancelRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::NotImplemented("Cancel/Void flow".to_string()).into()) + } +} + +impl ConnectorIntegration for Elavon { + fn get_headers( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!("{}processxml.do", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let refund_amount = utils::convert_amount( + self.amount_converter, + req.request.minor_refund_amount, + req.request.currency, + )?; + + let connector_router_data = elavon::ElavonRouterData::from((refund_amount, req)); + let connector_req = elavon::ElavonRefundRequest::try_from(&connector_router_data)?; + router_env::logger::info!(raw_connector_request=?connector_req); + Ok(RequestContent::FormUrlEncoded(Box::new(struct_to_xml( + &connector_req, + )?))) + } + + fn build_request( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundExecuteType::get_headers( + self, req, connectors, + )?) + .set_body(types::RefundExecuteType::get_request_body( + self, req, connectors, + )?) + .build(); + Ok(Some(request)) + } + + fn handle_response( + &self, + data: &RefundsRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult, errors::ConnectorError> { + let response: elavon::ElavonPaymentsResponse = + utils::deserialize_xml_to_struct(&res.response)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Elavon { + fn get_headers( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!("{}processxml.do", self.base_url(connectors))) + } + fn get_request_body( + &self, + req: &RefundSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = elavon::SyncRequest::try_from(req)?; + router_env::logger::info!(raw_connector_request=?connector_req); + Ok(RequestContent::FormUrlEncoded(Box::new(struct_to_xml( + &connector_req, + )?))) + } + + fn build_request( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::RefundSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundSyncType::get_headers(self, req, connectors)?) + .set_body(types::RefundSyncType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &RefundSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: elavon::ElavonSyncResponse = utils::deserialize_xml_to_struct(&res.response)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +#[async_trait::async_trait] +impl webhooks::IncomingWebhook for Elavon { + fn get_webhook_object_reference_id( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_event_type( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_resource_object( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } +} + +impl ConnectorValidation for Elavon { + fn validate_connector_against_payment_request( + &self, + capture_method: Option, + _payment_method: PaymentMethod, + _pmt: Option, + ) -> CustomResult<(), errors::ConnectorError> { + let capture_method = capture_method.unwrap_or_default(); + match capture_method { + CaptureMethod::Automatic + | CaptureMethod::Manual + | CaptureMethod::SequentialAutomatic => Ok(()), + CaptureMethod::ManualMultiple | CaptureMethod::Scheduled => Err( + utils::construct_not_implemented_error_report(capture_method, self.id()), + ), + } + } + fn validate_mandate_payment( + &self, + pm_type: Option, + pm_data: PaymentMethodData, + ) -> CustomResult<(), errors::ConnectorError> { + let mandate_supported_pmd = std::collections::HashSet::from([PaymentMethodDataType::Card]); + utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) + } +} + +impl ConnectorSpecifications for Elavon {} diff --git a/crates/hyperswitch_connectors/src/connectors/elavon/transformers.rs b/crates/hyperswitch_connectors/src/connectors/elavon/transformers.rs new file mode 100644 index 000000000000..67561acf8db4 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/elavon/transformers.rs @@ -0,0 +1,599 @@ +use cards::CardNumber; +use common_enums::{enums, Currency}; +use common_utils::{pii::Email, types::StringMajorUnit}; +use error_stack::ResultExt; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::{PaymentsAuthorizeData, ResponseId}, + router_response_types::{MandateReference, PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, + RefundSyncRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::errors; +use masking::{ExposeInterface, Secret}; +use serde::{Deserialize, Serialize}; + +use crate::{ + types::{ + PaymentsCaptureResponseRouterData, PaymentsSyncResponseRouterData, + RefundsResponseRouterData, ResponseRouterData, + }, + utils::{CardData, PaymentsAuthorizeRequestData, RefundsRequestData, RouterData as _}, +}; + +pub struct ElavonRouterData { + pub amount: StringMajorUnit, + pub router_data: T, +} + +impl From<(StringMajorUnit, T)> for ElavonRouterData { + fn from((amount, item): (StringMajorUnit, T)) -> Self { + Self { + amount, + router_data: item, + } + } +} +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "lowercase")] +pub enum TransactionType { + CcSale, + CcAuthOnly, + CcComplete, + CcReturn, + TxnQuery, +} +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "UPPERCASE")] +pub enum SyncTransactionType { + Sale, + AuthOnly, + Return, +} + +#[derive(Debug, Serialize)] +#[serde(untagged)] +pub enum ElavonPaymentsRequest { + Card(CardPaymentRequest), + MandatePayment(MandatePaymentRequest), +} +#[derive(Debug, Serialize)] +pub struct CardPaymentRequest { + pub ssl_transaction_type: TransactionType, + pub ssl_account_id: Secret, + pub ssl_user_id: Secret, + pub ssl_pin: Secret, + pub ssl_amount: StringMajorUnit, + pub ssl_card_number: CardNumber, + pub ssl_exp_date: Secret, + pub ssl_cvv2cvc2: Secret, + pub ssl_email: Email, + #[serde(skip_serializing_if = "Option::is_none")] + pub ssl_add_token: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub ssl_get_token: Option, + pub ssl_transaction_currency: Currency, +} +#[derive(Debug, Serialize)] +pub struct MandatePaymentRequest { + pub ssl_transaction_type: TransactionType, + pub ssl_account_id: Secret, + pub ssl_user_id: Secret, + pub ssl_pin: Secret, + pub ssl_amount: StringMajorUnit, + pub ssl_email: Email, + pub ssl_token: Secret, +} + +impl TryFrom<&ElavonRouterData<&PaymentsAuthorizeRouterData>> for ElavonPaymentsRequest { + type Error = error_stack::Report; + fn try_from( + item: &ElavonRouterData<&PaymentsAuthorizeRouterData>, + ) -> Result { + let auth = ElavonAuthType::try_from(&item.router_data.connector_auth_type)?; + match item.router_data.request.payment_method_data.clone() { + PaymentMethodData::Card(req_card) => Ok(Self::Card(CardPaymentRequest { + ssl_transaction_type: match item.router_data.request.is_auto_capture()? { + true => TransactionType::CcSale, + false => TransactionType::CcAuthOnly, + }, + ssl_account_id: auth.account_id.clone(), + ssl_user_id: auth.user_id.clone(), + ssl_pin: auth.pin.clone(), + ssl_amount: item.amount.clone(), + ssl_card_number: req_card.card_number.clone(), + ssl_exp_date: req_card.get_expiry_date_as_mmyy()?, + ssl_cvv2cvc2: req_card.card_cvc, + ssl_email: item.router_data.get_billing_email()?, + ssl_add_token: match item.router_data.request.is_mandate_payment() { + true => Some("Y".to_string()), + false => None, + }, + ssl_get_token: match item.router_data.request.is_mandate_payment() { + true => Some("Y".to_string()), + false => None, + }, + ssl_transaction_currency: item.router_data.request.currency, + })), + PaymentMethodData::MandatePayment => Ok(Self::MandatePayment(MandatePaymentRequest { + ssl_transaction_type: match item.router_data.request.is_auto_capture()? { + true => TransactionType::CcSale, + false => TransactionType::CcAuthOnly, + }, + ssl_account_id: auth.account_id.clone(), + ssl_user_id: auth.user_id.clone(), + ssl_pin: auth.pin.clone(), + ssl_amount: item.amount.clone(), + ssl_email: item.router_data.get_billing_email()?, + ssl_token: Secret::new(item.router_data.request.get_connector_mandate_id()?), + })), + _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), + } + } +} + +pub struct ElavonAuthType { + pub(super) account_id: Secret, + pub(super) user_id: Secret, + pub(super) pin: Secret, +} + +impl TryFrom<&ConnectorAuthType> for ElavonAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &ConnectorAuthType) -> Result { + match auth_type { + ConnectorAuthType::SignatureKey { + api_key, + key1, + api_secret, + } => Ok(Self { + account_id: api_key.to_owned(), + user_id: key1.to_owned(), + pin: api_secret.to_owned(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +enum SslResult { + #[serde(rename = "0")] + ImportedBatchFile, + #[serde(other)] + DeclineOrUnauthorized, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum ElavonPaymentsResponse { + #[serde(rename = "txn")] + Success(PaymentResponse), + #[serde(rename = "txn")] + Error(ElavonErrorResponse), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ElavonErrorResponse { + error_code: String, + error_message: String, + error_name: String, +} +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PaymentResponse { + ssl_result: SslResult, + ssl_txn_id: String, + ssl_result_message: String, + ssl_token: Option>, +} + +impl + TryFrom< + ResponseRouterData, + > for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + F, + ElavonPaymentsResponse, + PaymentsAuthorizeData, + PaymentsResponseData, + >, + ) -> Result { + let status = get_payment_status(&item.response, item.data.request.is_auto_capture()?); + let response = match &item.response { + ElavonPaymentsResponse::Error(error) => Err(ErrorResponse { + code: error.error_code.clone(), + message: error.error_message.clone(), + reason: Some(error.error_message.clone()), + attempt_status: None, + connector_transaction_id: None, + status_code: item.http_code, + }), + ElavonPaymentsResponse::Success(response) => { + if status == enums::AttemptStatus::Failure { + Err(ErrorResponse { + code: response.ssl_result_message.clone(), + message: response.ssl_result_message.clone(), + reason: Some(response.ssl_result_message.clone()), + attempt_status: None, + connector_transaction_id: Some(response.ssl_txn_id.clone()), + status_code: item.http_code, + }) + } else { + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + response.ssl_txn_id.clone(), + ), + redirection_data: Box::new(None), + mandate_reference: Box::new(Some(MandateReference { + connector_mandate_id: response + .ssl_token + .as_ref() + .map(|secret| secret.clone().expose()), + payment_method_id: None, + mandate_metadata: None, + connector_mandate_request_reference_id: None, + })), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(response.ssl_txn_id.clone()), + incremental_authorization_allowed: None, + charge_id: None, + }) + } + } + }; + Ok(Self { + status, + response, + ..item.data + }) + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum TransactionSyncStatus { + PEN, // Pended + OPN, // Unpended / release / open + REV, // Review + STL, // Settled + PST, // Failed due to post-auth rule + FPR, // Failed due to fraud prevention rules + PRE, // Failed due to pre-auth rule +} + +#[derive(Debug, Serialize)] +#[serde(rename = "txn")] +pub struct PaymentsCaptureRequest { + pub ssl_transaction_type: TransactionType, + pub ssl_account_id: Secret, + pub ssl_user_id: Secret, + pub ssl_pin: Secret, + pub ssl_amount: StringMajorUnit, + pub ssl_txn_id: String, +} +#[derive(Debug, Serialize)] +#[serde(rename = "txn")] +pub struct PaymentsVoidRequest { + pub ssl_transaction_type: TransactionType, + pub ssl_account_id: Secret, + pub ssl_user_id: Secret, + pub ssl_pin: Secret, + pub ssl_txn_id: String, +} + +#[derive(Debug, Serialize)] +#[serde(rename = "txn")] +pub struct ElavonRefundRequest { + pub ssl_transaction_type: TransactionType, + pub ssl_account_id: Secret, + pub ssl_user_id: Secret, + pub ssl_pin: Secret, + pub ssl_amount: StringMajorUnit, + pub ssl_txn_id: String, +} + +#[derive(Debug, Serialize)] +#[serde(rename = "txn")] +pub struct SyncRequest { + pub ssl_transaction_type: TransactionType, + pub ssl_account_id: Secret, + pub ssl_user_id: Secret, + pub ssl_pin: Secret, + pub ssl_txn_id: String, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename = "txn")] +pub struct ElavonSyncResponse { + pub ssl_trans_status: TransactionSyncStatus, + pub ssl_transaction_type: SyncTransactionType, + pub ssl_txn_id: String, +} +impl TryFrom<&RefundSyncRouterData> for SyncRequest { + type Error = error_stack::Report; + fn try_from(item: &RefundSyncRouterData) -> Result { + let auth = ElavonAuthType::try_from(&item.connector_auth_type)?; + Ok(Self { + ssl_txn_id: item.request.get_connector_refund_id()?, + ssl_transaction_type: TransactionType::TxnQuery, + ssl_account_id: auth.account_id.clone(), + ssl_user_id: auth.user_id.clone(), + ssl_pin: auth.pin.clone(), + }) + } +} +impl TryFrom<&PaymentsSyncRouterData> for SyncRequest { + type Error = error_stack::Report; + fn try_from(item: &PaymentsSyncRouterData) -> Result { + let auth = ElavonAuthType::try_from(&item.connector_auth_type)?; + Ok(Self { + ssl_txn_id: item + .request + .connector_transaction_id + .get_connector_transaction_id() + .change_context(errors::ConnectorError::MissingConnectorTransactionID)?, + ssl_transaction_type: TransactionType::TxnQuery, + ssl_account_id: auth.account_id.clone(), + ssl_user_id: auth.user_id.clone(), + ssl_pin: auth.pin.clone(), + }) + } +} +impl TryFrom<&ElavonRouterData<&RefundsRouterData>> for ElavonRefundRequest { + type Error = error_stack::Report; + fn try_from(item: &ElavonRouterData<&RefundsRouterData>) -> Result { + let auth = ElavonAuthType::try_from(&item.router_data.connector_auth_type)?; + Ok(Self { + ssl_txn_id: item.router_data.request.connector_transaction_id.clone(), + ssl_amount: item.amount.clone(), + ssl_transaction_type: TransactionType::CcReturn, + ssl_account_id: auth.account_id.clone(), + ssl_user_id: auth.user_id.clone(), + ssl_pin: auth.pin.clone(), + }) + } +} + +impl TryFrom<&ElavonRouterData<&PaymentsCaptureRouterData>> for PaymentsCaptureRequest { + type Error = error_stack::Report; + fn try_from(item: &ElavonRouterData<&PaymentsCaptureRouterData>) -> Result { + let auth = ElavonAuthType::try_from(&item.router_data.connector_auth_type)?; + Ok(Self { + ssl_txn_id: item.router_data.request.connector_transaction_id.clone(), + ssl_amount: item.amount.clone(), + ssl_transaction_type: TransactionType::CcComplete, + ssl_account_id: auth.account_id.clone(), + ssl_user_id: auth.user_id.clone(), + ssl_pin: auth.pin.clone(), + }) + } +} + +impl TryFrom> for PaymentsSyncRouterData { + type Error = error_stack::Report; + fn try_from( + item: PaymentsSyncResponseRouterData, + ) -> Result { + Ok(Self { + status: get_sync_status(item.data.status, &item.response), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.ssl_txn_id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } +} +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.ssl_txn_id.clone(), + refund_status: get_refund_status(item.data.request.refund_status, &item.response), + }), + ..item.data + }) + } +} + +impl TryFrom> + for PaymentsCaptureRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: PaymentsCaptureResponseRouterData, + ) -> Result { + let status = map_payment_status(&item.response, enums::AttemptStatus::Charged); + let response = match &item.response { + ElavonPaymentsResponse::Error(error) => Err(ErrorResponse { + code: error.error_code.clone(), + message: error.error_message.clone(), + reason: Some(error.error_message.clone()), + attempt_status: None, + connector_transaction_id: None, + status_code: item.http_code, + }), + ElavonPaymentsResponse::Success(response) => { + if status == enums::AttemptStatus::Failure { + Err(ErrorResponse { + code: response.ssl_result_message.clone(), + message: response.ssl_result_message.clone(), + reason: Some(response.ssl_result_message.clone()), + attempt_status: None, + connector_transaction_id: None, + status_code: item.http_code, + }) + } else { + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + response.ssl_txn_id.clone(), + ), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(response.ssl_txn_id.clone()), + incremental_authorization_allowed: None, + charge_id: None, + }) + } + } + }; + Ok(Self { + status, + response, + ..item.data + }) + } +} +impl TryFrom> + for RefundsRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + let status = enums::RefundStatus::from(&item.response); + let response = match &item.response { + ElavonPaymentsResponse::Error(error) => Err(ErrorResponse { + code: error.error_code.clone(), + message: error.error_message.clone(), + reason: Some(error.error_message.clone()), + attempt_status: None, + connector_transaction_id: None, + status_code: item.http_code, + }), + ElavonPaymentsResponse::Success(response) => { + if status == enums::RefundStatus::Failure { + Err(ErrorResponse { + code: response.ssl_result_message.clone(), + message: response.ssl_result_message.clone(), + reason: Some(response.ssl_result_message.clone()), + attempt_status: None, + connector_transaction_id: None, + status_code: item.http_code, + }) + } else { + Ok(RefundsResponseData { + connector_refund_id: response.ssl_txn_id.clone(), + refund_status: enums::RefundStatus::from(&item.response), + }) + } + } + }; + Ok(Self { + response, + ..item.data + }) + } +} + +trait ElavonResponseValidator { + fn is_successful(&self) -> bool; +} +impl ElavonResponseValidator for ElavonPaymentsResponse { + fn is_successful(&self) -> bool { + matches!(self, Self::Success(response) if response.ssl_result == SslResult::ImportedBatchFile) + } +} + +fn map_payment_status( + item: &ElavonPaymentsResponse, + success_status: enums::AttemptStatus, +) -> enums::AttemptStatus { + if item.is_successful() { + success_status + } else { + enums::AttemptStatus::Failure + } +} + +impl From<&ElavonPaymentsResponse> for enums::RefundStatus { + fn from(item: &ElavonPaymentsResponse) -> Self { + if item.is_successful() { + Self::Success + } else { + Self::Failure + } + } +} +fn get_refund_status( + prev_status: enums::RefundStatus, + item: &ElavonSyncResponse, +) -> enums::RefundStatus { + match item.ssl_trans_status { + TransactionSyncStatus::REV | TransactionSyncStatus::OPN | TransactionSyncStatus::PEN => { + prev_status + } + TransactionSyncStatus::STL => enums::RefundStatus::Success, + TransactionSyncStatus::PST | TransactionSyncStatus::FPR | TransactionSyncStatus::PRE => { + enums::RefundStatus::Failure + } + } +} +impl From<&ElavonSyncResponse> for enums::AttemptStatus { + fn from(item: &ElavonSyncResponse) -> Self { + match item.ssl_trans_status { + TransactionSyncStatus::REV + | TransactionSyncStatus::OPN + | TransactionSyncStatus::PEN => Self::Pending, + TransactionSyncStatus::STL => match item.ssl_transaction_type { + SyncTransactionType::Sale => Self::Charged, + SyncTransactionType::AuthOnly => Self::Authorized, + SyncTransactionType::Return => Self::Pending, + }, + TransactionSyncStatus::PST + | TransactionSyncStatus::FPR + | TransactionSyncStatus::PRE => Self::Failure, + } + } +} +fn get_sync_status( + prev_status: enums::AttemptStatus, + item: &ElavonSyncResponse, +) -> enums::AttemptStatus { + match item.ssl_trans_status { + TransactionSyncStatus::REV | TransactionSyncStatus::OPN | TransactionSyncStatus::PEN => { + prev_status + } + TransactionSyncStatus::STL => match item.ssl_transaction_type { + SyncTransactionType::Sale => enums::AttemptStatus::Charged, + SyncTransactionType::AuthOnly => enums::AttemptStatus::Authorized, + SyncTransactionType::Return => enums::AttemptStatus::Pending, + }, + TransactionSyncStatus::PST | TransactionSyncStatus::FPR | TransactionSyncStatus::PRE => { + enums::AttemptStatus::Failure + } + } +} + +fn get_payment_status( + item: &ElavonPaymentsResponse, + is_auto_capture: bool, +) -> enums::AttemptStatus { + if item.is_successful() { + if is_auto_capture { + enums::AttemptStatus::Charged + } else { + enums::AttemptStatus::Authorized + } + } else { + enums::AttemptStatus::Failure + } +} diff --git a/crates/hyperswitch_connectors/src/connectors/fiserv.rs b/crates/hyperswitch_connectors/src/connectors/fiserv.rs index fc67cd2fde78..e80c57454a09 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiserv.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiserv.rs @@ -29,7 +29,10 @@ use hyperswitch_domain_models::{ }, }; use hyperswitch_interfaces::{ - api::{self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorValidation}, + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, configs::Connectors, consts, errors, events::connector_api_logs::ConnectorEvent, @@ -179,14 +182,17 @@ impl ConnectorCommon for Fiserv { } impl ConnectorValidation for Fiserv { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( utils::construct_not_implemented_error_report(capture_method, self.id()), ), @@ -782,3 +788,5 @@ impl webhooks::IncomingWebhook for Fiserv { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for Fiserv {} diff --git a/crates/hyperswitch_connectors/src/connectors/fiserv/transformers.rs b/crates/hyperswitch_connectors/src/connectors/fiserv/transformers.rs index b2cbd570b4f0..f4084525410e 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiserv/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiserv/transformers.rs @@ -149,7 +149,9 @@ impl TryFrom<&FiservRouterData<&types::PaymentsAuthorizeRouterData>> for FiservP let transaction_details = TransactionDetails { capture_flag: Some(matches!( item.router_data.request.capture_method, - Some(enums::CaptureMethod::Automatic) | None + Some(enums::CaptureMethod::Automatic) + | Some(enums::CaptureMethod::SequentialAutomatic) + | None )), reversal_reason_code: None, merchant_transaction_id: item.router_data.connector_request_reference_id.clone(), @@ -195,11 +197,13 @@ impl TryFrom<&FiservRouterData<&types::PaymentsAuthorizeRouterData>> for FiservP | PaymentMethodData::Reward | PaymentMethodData::RealTimePayment(_) | PaymentMethodData::Upi(_) + | PaymentMethodData::MobilePayment(_) | PaymentMethodData::Voucher(_) | PaymentMethodData::GiftCard(_) | PaymentMethodData::OpenBanking(_) | PaymentMethodData::CardToken(_) - | PaymentMethodData::NetworkToken(_) => { + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("fiserv"), )) @@ -373,8 +377,8 @@ impl TryFrom TryFrom, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( utils::construct_not_implemented_error_report(capture_method, self.id()), ), @@ -784,3 +790,5 @@ impl webhooks::IncomingWebhook for Fiservemea { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for Fiservemea {} diff --git a/crates/hyperswitch_connectors/src/connectors/fiservemea/transformers.rs b/crates/hyperswitch_connectors/src/connectors/fiservemea/transformers.rs index 7f88903e8670..24934273a449 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiservemea/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiservemea/transformers.rs @@ -103,13 +103,16 @@ impl TryFrom<&FiservemeaRouterData<&PaymentsAuthorizeRouterData>> for Fiservemea }, security_code: req_card.card_cvc, }; - let request_type = if item.router_data.request.capture_method - == Some(enums::CaptureMethod::Automatic) - { + let request_type = if matches!( + item.router_data.request.capture_method, + Some(enums::CaptureMethod::Automatic) + | Some(enums::CaptureMethod::SequentialAutomatic) + ) { FiservemeaRequestType::PaymentCardSaleTransaction } else { FiservemeaRequestType::PaymentCardPreAuthTransaction }; + Ok(Self { request_type, merchant_transaction_id: item @@ -361,8 +364,8 @@ impl TryFrom(data: &[u8]) -> Result where @@ -196,19 +205,31 @@ pub fn build_form_from_struct(data: T) -> Result, + _payment_method: PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - CaptureMethod::Automatic | CaptureMethod::Manual => Ok(()), + CaptureMethod::Automatic + | CaptureMethod::Manual + | CaptureMethod::SequentialAutomatic => Ok(()), CaptureMethod::ManualMultiple | CaptureMethod::Scheduled => Err( utils::construct_not_implemented_error_report(capture_method, self.id()), ), } } + fn validate_mandate_payment( + &self, + pm_type: Option, + pm_data: PaymentMethodData, + ) -> CustomResult<(), errors::ConnectorError> { + let mandate_supported_pmd: HashSet = + HashSet::from([PaymentMethodDataType::Card]); + utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) + } } impl ConnectorIntegration for Fiuu { @@ -233,16 +254,18 @@ impl ConnectorIntegration CustomResult { - match req.payment_method { - PaymentMethod::RealTimePayment => { - let base_url = connectors.fiuu.third_base_url.clone(); - Ok(format!("{}RMS/API/staticqr/index.php", base_url)) - } - _ => Ok(format!( + let url = if req.request.off_session == Some(true) { + format!( + "{}/RMS/API/Recurring/input_v7.php", + self.base_url(connectors) + ) + } else { + format!( "{}RMS/API/Direct/1.4.0/index.php", self.base_url(connectors) - )), - } + ) + }; + Ok(url) } fn get_request_body( @@ -257,9 +280,15 @@ impl ConnectorIntegration for Fiu event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: fiuu::FiuuPaymentSyncResponse = parse_response(&res.response)?; - event_builder.map(|i| i.set_response_body(&response)); - router_env::logger::info!(connector_response=?response); - - RouterData::try_from(ResponseRouterData { - response, - data: data.clone(), - http_code: res.status_code, - }) + match res.headers { + Some(headers) => { + let content_header = utils::get_http_header("Content-type", &headers) + .attach_printable("Missing content type in headers") + .change_context(errors::ConnectorError::ResponseHandlingFailed)?; + let response: fiuu::FiuuPaymentResponse = if content_header + == "text/plain;charset=UTF-8" + { + parse_response(&res.response) + } else { + Err(errors::ConnectorError::ResponseDeserializationFailed) + .attach_printable(format!("Expected content type to be text/plain;charset=UTF-8 , but received different content type as {content_header} in response"))? + }?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } + None => { + // We don't get headers for payment webhook response handling + let response: fiuu::FiuuPaymentResponse = res + .response + .parse_struct("fiuu::FiuuPaymentResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } + } } fn get_error_response( @@ -658,24 +716,218 @@ impl ConnectorIntegration for Fiuu { #[async_trait::async_trait] impl webhooks::IncomingWebhook for Fiuu { - fn get_webhook_object_reference_id( + fn get_webhook_source_verification_algorithm( &self, _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Ok(Box::new(crypto::Md5)) + } + + fn get_webhook_source_verification_signature( + &self, + request: &webhooks::IncomingWebhookRequestDetails<'_>, + _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, + ) -> CustomResult, errors::ConnectorError> { + let header = utils::get_header_key_value("content-type", request.headers)?; + let resource: FiuuWebhooksResponse = if header == "application/x-www-form-urlencoded" { + serde_urlencoded::from_bytes::(request.body) + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)? + } else { + request + .body + .parse_struct("fiuu::FiuuWebhooksResponse") + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)? + }; + + let signature = match resource { + FiuuWebhooksResponse::FiuuWebhookPaymentResponse(webhooks_payment_response) => { + webhooks_payment_response.skey + } + FiuuWebhooksResponse::FiuuWebhookRefundResponse(webhooks_refunds_response) => { + webhooks_refunds_response.signature + } + }; + hex::decode(signature.expose()) + .change_context(errors::ConnectorError::WebhookVerificationSecretInvalid) + } + + fn get_webhook_source_verification_message( + &self, + request: &webhooks::IncomingWebhookRequestDetails<'_>, + _merchant_id: &common_utils::id_type::MerchantId, + connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, + ) -> CustomResult, errors::ConnectorError> { + let header = utils::get_header_key_value("content-type", request.headers)?; + let resource: FiuuWebhooksResponse = if header == "application/x-www-form-urlencoded" { + serde_urlencoded::from_bytes::(request.body) + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)? + } else { + request + .body + .parse_struct("fiuu::FiuuWebhooksResponse") + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)? + }; + let verification_message = match resource { + FiuuWebhooksResponse::FiuuWebhookPaymentResponse(webhooks_payment_response) => { + let key0 = format!( + "{}{}{}{}{}{}", + webhooks_payment_response.tran_id, + webhooks_payment_response.order_id, + webhooks_payment_response.status, + webhooks_payment_response.domain.clone().peek(), + webhooks_payment_response.amount.get_amount_as_string(), + webhooks_payment_response.currency + ); + let md5_key0 = hex::encode( + crypto::Md5 + .generate_digest(key0.as_bytes()) + .change_context(errors::ConnectorError::RequestEncodingFailed)?, + ); + let key1 = format!( + "{}{}{}{}{}", + webhooks_payment_response.paydate, + webhooks_payment_response.domain.peek(), + md5_key0, + webhooks_payment_response.appcode.peek(), + String::from_utf8_lossy(&connector_webhook_secrets.secret) + ); + key1 + } + FiuuWebhooksResponse::FiuuWebhookRefundResponse(webhooks_refunds_response) => { + format!( + "{}{}{}{}{}{}{}{}", + webhooks_refunds_response.refund_type, + webhooks_refunds_response.merchant_id.peek(), + webhooks_refunds_response.ref_id, + webhooks_refunds_response.refund_id, + webhooks_refunds_response.txn_id, + webhooks_refunds_response.amount.get_amount_as_string(), + webhooks_refunds_response.status, + String::from_utf8_lossy(&connector_webhook_secrets.secret) + ) + } + }; + Ok(verification_message.as_bytes().to_vec()) + } + + fn get_webhook_object_reference_id( + &self, + request: &webhooks::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let header = utils::get_header_key_value("content-type", request.headers)?; + let resource: FiuuWebhooksResponse = if header == "application/x-www-form-urlencoded" { + serde_urlencoded::from_bytes::(request.body) + .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)? + } else { + request + .body + .parse_struct("fiuu::FiuuWebhooksResponse") + .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)? + }; + let resource_id = match resource { + FiuuWebhooksResponse::FiuuWebhookPaymentResponse(webhooks_payment_response) => { + api_models::webhooks::ObjectReferenceId::PaymentId( + api_models::payments::PaymentIdType::PaymentAttemptId( + webhooks_payment_response.order_id, + ), + ) + } + FiuuWebhooksResponse::FiuuWebhookRefundResponse(webhooks_refunds_response) => { + api_models::webhooks::ObjectReferenceId::RefundId( + api_models::webhooks::RefundIdType::ConnectorRefundId( + webhooks_refunds_response.refund_id, + ), + ) + } + }; + Ok(resource_id) } fn get_webhook_event_type( &self, - _request: &webhooks::IncomingWebhookRequestDetails<'_>, + request: &webhooks::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let header = utils::get_header_key_value("content-type", request.headers)?; + let resource: FiuuWebhooksResponse = if header == "application/x-www-form-urlencoded" { + serde_urlencoded::from_bytes::(request.body) + .change_context(errors::ConnectorError::WebhookEventTypeNotFound)? + } else { + request + .body + .parse_struct("fiuu::FiuuWebhooksResponse") + .change_context(errors::ConnectorError::WebhookEventTypeNotFound)? + }; + + match resource { + FiuuWebhooksResponse::FiuuWebhookPaymentResponse(webhooks_payment_response) => Ok( + api_models::webhooks::IncomingWebhookEvent::from(webhooks_payment_response.status), + ), + FiuuWebhooksResponse::FiuuWebhookRefundResponse(webhooks_refunds_response) => Ok( + api_models::webhooks::IncomingWebhookEvent::from(webhooks_refunds_response.status), + ), + } } fn get_webhook_resource_object( &self, - _request: &webhooks::IncomingWebhookRequestDetails<'_>, + request: &webhooks::IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let header = utils::get_header_key_value("content-type", request.headers)?; + let payload: FiuuWebhooksResponse = if header == "application/x-www-form-urlencoded" { + serde_urlencoded::from_bytes::(request.body) + .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)? + } else { + request + .body + .parse_struct("fiuu::FiuuWebhooksResponse") + .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)? + }; + + match payload.clone() { + FiuuWebhooksResponse::FiuuWebhookPaymentResponse(webhook_payment_response) => Ok( + Box::new(fiuu::FiuuPaymentResponse::FiuuWebhooksPaymentResponse( + webhook_payment_response, + )), + ), + FiuuWebhooksResponse::FiuuWebhookRefundResponse(webhook_refund_response) => { + Ok(Box::new(fiuu::FiuuRefundSyncResponse::Webhook( + webhook_refund_response, + ))) + } + } + } + + fn get_mandate_details( + &self, + request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult< + Option, + errors::ConnectorError, + > { + let webhook_payment_response: transformers::FiuuWebhooksPaymentResponse = + serde_urlencoded::from_bytes::(request.body) + .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?; + let mandate_reference = webhook_payment_response.extra_parameters.as_ref().and_then(|extra_p| { + let mandate_token: Result = serde_json::from_str(extra_p); + match mandate_token { + Ok(token) => { + token.token.as_ref().map(|token| hyperswitch_domain_models::router_flow_types::ConnectorMandateDetails { + connector_mandate_id:token.clone(), + }) + } + Err(err) => { + router_env::logger::warn!( + "Failed to convert 'extraP' from fiuu webhook response to fiuu::ExtraParameters. \ + Input: '{}', Error: {}", + extra_p, + err + ); + None + } + } + }); + Ok(mandate_reference) } } + +impl ConnectorSpecifications for Fiuu {} diff --git a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs index 561447fc075c..143cd1aa0474 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs @@ -4,36 +4,52 @@ use api_models::payments; use cards::CardNumber; use common_enums::{enums, BankNames, CaptureMethod, Currency}; use common_utils::{ - crypto::GenerateDigest, + crypto::{self, GenerateDigest}, errors::CustomResult, ext_traits::Encode, + pii::Email, request::Method, types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector}, }; use error_stack::{Report, ResultExt}; use hyperswitch_domain_models::{ - payment_method_data::{BankRedirectData, PaymentMethodData, RealTimePaymentData}, - router_data::{ConnectorAuthType, ErrorResponse, RouterData}, + payment_method_data::{ + BankRedirectData, Card, GooglePayWalletData, PaymentMethodData, RealTimePaymentData, + WalletData, + }, + router_data::{ + ApplePayPredecryptData, ConnectorAuthType, ErrorResponse, PaymentMethodToken, RouterData, + }, router_flow_types::refunds::{Execute, RSync}, router_request_types::{PaymentsAuthorizeData, ResponseId}, - router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData}, + router_response_types::{ + MandateReference, PaymentsResponseData, RedirectForm, RefundsResponseData, + }, types::{ PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, }, }; -use hyperswitch_interfaces::errors; -use masking::{PeekInterface, Secret}; +use hyperswitch_interfaces::{consts, errors}; +use masking::{ExposeInterface, PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use strum::Display; use url::Url; +// These needs to be accepted from SDK, need to be done after 1.0.0 stability as API contract will change +const GOOGLEPAY_API_VERSION_MINOR: u8 = 0; +const GOOGLEPAY_API_VERSION: u8 = 2; + use crate::{ types::{ PaymentsCancelResponseRouterData, PaymentsCaptureResponseRouterData, PaymentsSyncResponseRouterData, RefundsResponseRouterData, ResponseRouterData, }, - utils::{self, PaymentsAuthorizeRequestData, QrImage, RouterData as _}, + unimplemented_payment_method, + utils::{ + self, ApplePayDecrypt, PaymentsAuthorizeRequestData, QrImage, RefundsRequestData, + RouterData as _, + }, }; pub struct FiuuRouterData { @@ -74,9 +90,9 @@ impl TryFrom<&ConnectorAuthType> for FiuuAuthType { } } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "UPPERCASE")] -enum TxnType { +pub enum TxnType { Sals, Auts, } @@ -85,25 +101,26 @@ impl TryFrom> for TxnType { type Error = Report; fn try_from(capture_method: Option) -> Result { match capture_method { - Some(CaptureMethod::Automatic) => Ok(Self::Sals), + Some(CaptureMethod::Automatic) | Some(CaptureMethod::SequentialAutomatic) | None => { + Ok(Self::Sals) + } Some(CaptureMethod::Manual) => Ok(Self::Auts), _ => Err(errors::ConnectorError::CaptureMethodNotSupported.into()), } } } -#[derive(Serialize, Deserialize, Display, Debug)] -#[serde(rename_all = "UPPERCASE")] +#[derive(Serialize, Deserialize, Display, Debug, Clone)] enum TxnChannel { #[serde(rename = "CREDITAN")] #[strum(serialize = "CREDITAN")] Creditan, - #[serde(rename = "DuitNowSQR")] - #[strum(serialize = "DuitNowSQR")] - DuitNowSqr, + #[serde(rename = "RPP_DUITNOWQR")] + #[strum(serialize = "RPP_DUITNOWQR")] + RppDuitNowQr, } -#[derive(Serialize, Deserialize, Display, Debug)] +#[derive(Serialize, Deserialize, Display, Debug, Clone)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[strum(serialize_all = "SCREAMING_SNAKE_CASE")] pub enum FPXTxnChannel { @@ -157,63 +174,204 @@ impl TryFrom for FPXTxnChannel { } } -#[derive(Serialize, Debug, Deserialize)] -#[serde(untagged)] -#[serde(rename_all = "PascalCase")] -pub enum FiuuPaymentsRequest { - QRPaymentRequest(FiuuQRPaymentRequest), - CardPaymentRequest(FiuuCardPaymentRequest), - FpxPaymentRequest(FiuuFPXPyamentRequest), +#[derive(Serialize, Debug, Clone)] +pub struct FiuuMandateRequest { + #[serde(rename = "0")] + mandate_request: Secret, +} + +#[derive(Serialize, Debug, Clone)] +pub struct FiuuRecurringRequest { + record_type: FiuuRecordType, + merchant_id: Secret, + token: Secret, + order_id: String, + currency: Currency, + amount: StringMajorUnit, + billing_name: Secret, + email: Email, + verify_key: Secret, +} + +#[derive(Serialize, Debug, Clone, strum::Display)] +pub enum FiuuRecordType { + T, +} + +impl TryFrom<&FiuuRouterData<&PaymentsAuthorizeRouterData>> for FiuuMandateRequest { + type Error = Report; + fn try_from(item: &FiuuRouterData<&PaymentsAuthorizeRouterData>) -> Result { + let auth: FiuuAuthType = FiuuAuthType::try_from(&item.router_data.connector_auth_type)?; + let record_type = FiuuRecordType::T; + let merchant_id = auth.merchant_id; + let order_id = item.router_data.connector_request_reference_id.clone(); + let currency = item.router_data.request.currency; + let amount = item.amount.clone(); + let billing_name = item + .router_data + .request + .get_card_holder_name_from_additional_payment_method_data()?; + let email = item.router_data.get_billing_email()?; + let token = Secret::new(item.router_data.request.get_connector_mandate_id()?); + let verify_key = auth.verify_key; + let recurring_request = FiuuRecurringRequest { + record_type: record_type.clone(), + merchant_id: merchant_id.clone(), + token: token.clone(), + order_id: order_id.clone(), + currency, + amount: amount.clone(), + billing_name: billing_name.clone(), + email: email.clone(), + verify_key: verify_key.clone(), + }; + let check_sum = calculate_check_sum(recurring_request)?; + let mandate_request = format!( + "{}|{}||{}|{}|{}|{}|{}|{}|||{}", + record_type, + merchant_id.peek(), + token.peek(), + order_id, + currency, + amount.get_amount_as_string(), + billing_name.peek(), + email.peek(), + check_sum.peek() + ); + Ok(Self { + mandate_request: mandate_request.into(), + }) + } +} + +pub fn calculate_check_sum( + req: FiuuRecurringRequest, +) -> CustomResult, errors::ConnectorError> { + let formatted_string = format!( + "{}{}{}{}{}{}{}", + req.record_type, + req.merchant_id.peek(), + req.token.peek(), + req.order_id, + req.currency, + req.amount.get_amount_as_string(), + req.verify_key.peek() + ); + Ok(Secret::new(hex::encode( + crypto::Md5 + .generate_digest(formatted_string.as_bytes()) + .change_context(errors::ConnectorError::RequestEncodingFailed)?, + ))) } -#[derive(Serialize, Debug, Deserialize)] +#[derive(Serialize, Debug, Clone)] #[serde(rename_all = "PascalCase")] -pub struct FiuuFPXPyamentRequest { +pub struct FiuuPaymentRequest { #[serde(rename = "MerchantID")] merchant_id: Secret, reference_no: String, txn_type: TxnType, - txn_channel: FPXTxnChannel, txn_currency: Currency, txn_amount: StringMajorUnit, signature: Secret, #[serde(rename = "ReturnURL")] return_url: Option, + #[serde(rename = "NotificationURL")] + notification_url: Option, + #[serde(flatten)] + payment_method_data: FiuuPaymentMethodData, } -#[derive(Serialize, Debug, Deserialize)] -pub struct FiuuQRPaymentRequest { - #[serde(rename = "merchantID")] - merchant_id: Secret, - channel: TxnChannel, - orderid: String, - currency: Currency, - amount: StringMajorUnit, - checksum: Secret, + +#[derive(Serialize, Debug, Clone)] +#[serde(untagged)] +pub enum FiuuPaymentMethodData { + FiuuQRData(Box), + FiuuCardData(Box), + FiuuFpxData(Box), + FiuuGooglePayData(Box), + FiuuApplePayData(Box), } -#[derive(Serialize, Debug, Deserialize)] +#[derive(Serialize, Debug, Clone)] #[serde(rename_all = "PascalCase")] -pub struct FiuuCardPaymentRequest { - #[serde(rename = "MerchantID")] - merchant_id: Secret, - reference_no: String, - txn_type: TxnType, +pub struct FiuuFPXData { + #[serde(rename = "non_3DS")] + non_3ds: i32, + txn_channel: FPXTxnChannel, +} +#[derive(Serialize, Debug, Clone)] +#[serde(rename_all = "PascalCase")] +pub struct FiuuQRData { + txn_channel: TxnChannel, +} + +#[derive(Serialize, Debug, Clone)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub struct FiuuCardData { + #[serde(rename = "non_3DS")] + non_3ds: i32, + #[serde(rename = "TxnChannel")] txn_channel: TxnChannel, - txn_currency: Currency, - txn_amount: StringMajorUnit, - signature: Secret, - #[serde(rename = "CC_PAN")] cc_pan: CardNumber, - #[serde(rename = "CC_CVV2")] cc_cvv2: Secret, - #[serde(rename = "CC_MONTH")] cc_month: Secret, - #[serde(rename = "CC_YEAR")] cc_year: Secret, + #[serde(rename = "mpstokenstatus")] + mps_token_status: Option, + #[serde(rename = "CustEmail")] + customer_email: Option, +} + +#[derive(Serialize, Debug, Clone)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub struct FiuuApplePayData { + #[serde(rename = "TxnChannel")] + txn_channel: TxnChannel, + cc_month: Secret, + cc_year: Secret, + cc_token: Secret, + eci: Option, + token_cryptogram: Secret, + token_type: FiuuTokenType, + #[serde(rename = "non_3DS")] + non_3ds: i32, +} + +#[derive(Serialize, Debug, Clone)] +#[serde(rename_all = "PascalCase")] +pub enum FiuuTokenType { + ApplePay, + GooglePay, +} + +#[derive(Serialize, Debug, Clone)] +#[serde(rename_all = "PascalCase")] +pub struct FiuuGooglePayData { + txn_channel: TxnChannel, + #[serde(rename = "GooglePay[apiVersion]")] + api_version: u8, + #[serde(rename = "GooglePay[apiVersionMinor]")] + api_version_minor: u8, + #[serde(rename = "GooglePay[paymentMethodData][info][assuranceDetails][accountVerified]")] + account_verified: Option, + #[serde( + rename = "GooglePay[paymentMethodData][info][assuranceDetails][cardHolderAuthenticated]" + )] + card_holder_authenticated: Option, + #[serde(rename = "GooglePay[paymentMethodData][info][cardDetails]")] + card_details: String, + #[serde(rename = "GooglePay[paymentMethodData][info][cardNetwork]")] + card_network: String, + #[serde(rename = "GooglePay[paymentMethodData][tokenizationData][token]")] + token: Secret, + #[serde(rename = "GooglePay[paymentMethodData][tokenizationData][type]")] + tokenization_data_type: Secret, + #[serde(rename = "GooglePay[paymentMethodData][type]")] + pm_type: String, + #[serde(rename = "SCREAMING_SNAKE_CASE")] + token_type: FiuuTokenType, #[serde(rename = "non_3DS")] non_3ds: i32, - #[serde(rename = "ReturnURL")] - return_url: Option, } pub fn calculate_signature( @@ -221,14 +379,14 @@ pub fn calculate_signature( ) -> Result, Report> { let message = signature_data.as_bytes(); let encoded_data = hex::encode( - common_utils::crypto::Md5 + crypto::Md5 .generate_digest(message) .change_context(errors::ConnectorError::RequestEncodingFailed)?, ); Ok(Secret::new(encoded_data)) } -impl TryFrom<&FiuuRouterData<&PaymentsAuthorizeRouterData>> for FiuuPaymentsRequest { +impl TryFrom<&FiuuRouterData<&PaymentsAuthorizeRouterData>> for FiuuPaymentRequest { type Error = Report; fn try_from(item: &FiuuRouterData<&PaymentsAuthorizeRouterData>) -> Result { let auth = FiuuAuthType::try_from(&item.router_data.connector_auth_type)?; @@ -237,79 +395,231 @@ impl TryFrom<&FiuuRouterData<&PaymentsAuthorizeRouterData>> for FiuuPaymentsRequ let txn_amount = item.amount.clone(); let reference_no = item.router_data.connector_request_reference_id.clone(); let verify_key = auth.verify_key.peek().to_string(); - match item.router_data.request.payment_method_data.clone() { - PaymentMethodData::Card(req_card) => { - let signature = calculate_signature(format!( - "{}{merchant_id}{reference_no}{verify_key}", - txn_amount.get_amount_as_string() - ))?; - - Ok(Self::CardPaymentRequest(FiuuCardPaymentRequest { - merchant_id: auth.merchant_id, - reference_no, - txn_type: match item.router_data.request.is_auto_capture()? { - true => TxnType::Sals, - false => TxnType::Auts, - }, - txn_channel: TxnChannel::Creditan, - txn_currency, - txn_amount, - signature, - cc_pan: req_card.card_number, - cc_cvv2: req_card.card_cvc, - cc_month: req_card.card_exp_month, - cc_year: req_card.card_exp_year, - non_3ds: match item.router_data.is_three_ds() { - false => 1, - true => 0, - }, - return_url: item.router_data.request.router_return_url.clone(), - })) + let signature = calculate_signature(format!( + "{}{merchant_id}{reference_no}{verify_key}", + txn_amount.get_amount_as_string() + ))?; + let txn_type = match item.router_data.request.is_auto_capture()? { + true => TxnType::Sals, + false => TxnType::Auts, + }; + let return_url = item.router_data.request.router_return_url.clone(); + let non_3ds = match item.router_data.is_three_ds() { + false => 1, + true => 0, + }; + let notification_url = Some( + Url::parse(&item.router_data.request.get_webhook_url()?) + .change_context(errors::ConnectorError::RequestEncodingFailed)?, + ); + let payment_method_data = match item.router_data.request.payment_method_data { + PaymentMethodData::Card(ref card) => { + FiuuPaymentMethodData::try_from((card, item.router_data)) } - PaymentMethodData::RealTimePayment(real_time_payment_data) => { - match *real_time_payment_data { + PaymentMethodData::RealTimePayment(ref real_time_payment_data) => { + match *real_time_payment_data.clone() { RealTimePaymentData::DuitNow {} => { - Ok(Self::QRPaymentRequest(FiuuQRPaymentRequest { - merchant_id: auth.merchant_id, - channel: TxnChannel::DuitNowSqr, - orderid: reference_no.clone(), - currency: txn_currency, - amount: txn_amount.clone(), - checksum: calculate_signature(format!( - "{merchant_id}{}{reference_no}{txn_currency}{}{verify_key}", - TxnChannel::DuitNowSqr, - txn_amount.get_amount_as_string() - ))?, - })) + Ok(FiuuPaymentMethodData::FiuuQRData(Box::new(FiuuQRData { + txn_channel: TxnChannel::RppDuitNowQr, + }))) } RealTimePaymentData::Fps {} | RealTimePaymentData::PromptPay {} - | RealTimePaymentData::VietQr {} => Err( - errors::ConnectorError::NotImplemented("Payment methods".to_string()) - .into(), - ), + | RealTimePaymentData::VietQr {} => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("fiuu"), + ) + .into()) + } } } - PaymentMethodData::BankRedirect(BankRedirectData::OnlineBankingFpx { issuer }) => { - Ok(Self::FpxPaymentRequest(FiuuFPXPyamentRequest { - merchant_id: auth.merchant_id.clone(), - reference_no: reference_no.clone(), - txn_type: match item.router_data.request.is_auto_capture()? { - true => TxnType::Sals, - false => TxnType::Auts, - }, - txn_channel: FPXTxnChannel::try_from(issuer)?, - txn_currency, - txn_amount: txn_amount.clone(), - signature: calculate_signature(format!( - "{}{merchant_id}{reference_no}{verify_key}", - txn_amount.get_amount_as_string() - ))?, - return_url: item.router_data.request.router_return_url.clone(), - })) + PaymentMethodData::BankRedirect(ref bank_redirect_data) => match bank_redirect_data { + BankRedirectData::OnlineBankingFpx { ref issuer } => { + Ok(FiuuPaymentMethodData::FiuuFpxData(Box::new(FiuuFPXData { + txn_channel: FPXTxnChannel::try_from(*issuer)?, + non_3ds, + }))) + } + BankRedirectData::BancontactCard { .. } + | BankRedirectData::Bizum {} + | BankRedirectData::Blik { .. } + | BankRedirectData::Eps { .. } + | BankRedirectData::Giropay { .. } + | BankRedirectData::Ideal { .. } + | BankRedirectData::Interac { .. } + | BankRedirectData::OnlineBankingCzechRepublic { .. } + | BankRedirectData::OnlineBankingFinland { .. } + | BankRedirectData::OnlineBankingPoland { .. } + | BankRedirectData::OnlineBankingSlovakia { .. } + | BankRedirectData::OpenBankingUk { .. } + | BankRedirectData::Przelewy24 { .. } + | BankRedirectData::Sofort { .. } + | BankRedirectData::Trustly { .. } + | BankRedirectData::OnlineBankingThailand { .. } + | BankRedirectData::LocalBankRedirect {} => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("fiuu"), + ) + .into()) + } + }, + PaymentMethodData::Wallet(ref wallet_data) => match wallet_data { + WalletData::GooglePay(google_pay_data) => { + FiuuPaymentMethodData::try_from(google_pay_data) + } + WalletData::ApplePay(_apple_pay_data) => { + let payment_method_token = item.router_data.get_payment_method_token()?; + match payment_method_token { + PaymentMethodToken::Token(_) => { + Err(unimplemented_payment_method!("Apple Pay", "Manual", "Fiuu"))? + } + PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { + FiuuPaymentMethodData::try_from(decrypt_data) + } + PaymentMethodToken::PazeDecrypt(_) => { + Err(unimplemented_payment_method!("Paze", "Fiuu"))? + } + } + } + WalletData::AliPayQr(_) + | WalletData::AliPayRedirect(_) + | WalletData::AliPayHkRedirect(_) + | WalletData::MomoRedirect(_) + | WalletData::KakaoPayRedirect(_) + | WalletData::GoPayRedirect(_) + | WalletData::GcashRedirect(_) + | WalletData::ApplePayRedirect(_) + | WalletData::ApplePayThirdPartySdk(_) + | WalletData::DanaRedirect {} + | WalletData::GooglePayRedirect(_) + | WalletData::GooglePayThirdPartySdk(_) + | WalletData::MbWayRedirect(_) + | WalletData::MobilePayRedirect(_) + | WalletData::PaypalRedirect(_) + | WalletData::PaypalSdk(_) + | WalletData::Paze(_) + | WalletData::SamsungPay(_) + | WalletData::TwintRedirect {} + | WalletData::VippsRedirect {} + | WalletData::TouchNGoRedirect(_) + | WalletData::WeChatPayRedirect(_) + | WalletData::WeChatPayQr(_) + | WalletData::CashappQr(_) + | WalletData::SwishQr(_) + | WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("fiuu"), + ) + .into()), + }, + PaymentMethodData::CardRedirect(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Reward + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("fiuu"), + ) + .into()) } - _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), - } + }?; + + Ok(Self { + merchant_id: auth.merchant_id, + reference_no, + txn_type, + txn_currency, + txn_amount, + return_url, + payment_method_data, + signature, + notification_url, + }) + } +} + +impl TryFrom<(&Card, &PaymentsAuthorizeRouterData)> for FiuuPaymentMethodData { + type Error = Report; + fn try_from( + (req_card, item): (&Card, &PaymentsAuthorizeRouterData), + ) -> Result { + let (mps_token_status, customer_email) = + if item.request.is_customer_initiated_mandate_payment() { + (Some(1), Some(item.get_billing_email()?)) + } else { + (None, None) + }; + let non_3ds = match item.is_three_ds() { + false => 1, + true => 0, + }; + Ok(Self::FiuuCardData(Box::new(FiuuCardData { + txn_channel: TxnChannel::Creditan, + non_3ds, + cc_pan: req_card.card_number.clone(), + cc_cvv2: req_card.card_cvc.clone(), + cc_month: req_card.card_exp_month.clone(), + cc_year: req_card.card_exp_year.clone(), + mps_token_status, + customer_email, + }))) + } +} + +impl TryFrom<&GooglePayWalletData> for FiuuPaymentMethodData { + type Error = Report; + fn try_from(data: &GooglePayWalletData) -> Result { + Ok(Self::FiuuGooglePayData(Box::new(FiuuGooglePayData { + txn_channel: TxnChannel::Creditan, + api_version: GOOGLEPAY_API_VERSION, + api_version_minor: GOOGLEPAY_API_VERSION_MINOR, + account_verified: data + .info + .assurance_details + .as_ref() + .map(|details| details.account_verified), + card_holder_authenticated: data + .info + .assurance_details + .as_ref() + .map(|details| details.card_holder_authenticated), + card_details: data.info.card_details.clone(), + card_network: data.info.card_network.clone(), + token: data.tokenization_data.token.clone().into(), + tokenization_data_type: data.tokenization_data.token_type.clone().into(), + pm_type: data.pm_type.clone(), + token_type: FiuuTokenType::GooglePay, + // non_3ds field Applicable to card processing via specific processor using specific currency for pre-approved partner only. + // Equal to 0 by default and 1 for non-3DS transaction, That is why it is hardcoded to 1 for googlepay transactions. + non_3ds: 1, + }))) + } +} + +impl TryFrom> for FiuuPaymentMethodData { + type Error = Report; + fn try_from(decrypt_data: Box) -> Result { + Ok(Self::FiuuApplePayData(Box::new(FiuuApplePayData { + txn_channel: TxnChannel::Creditan, + cc_month: decrypt_data.get_expiry_month()?, + cc_year: decrypt_data.get_four_digit_expiry_year()?, + cc_token: decrypt_data.application_primary_account_number, + eci: decrypt_data.payment_data.eci_indicator, + token_cryptogram: decrypt_data.payment_data.online_payment_cryptogram, + token_type: FiuuTokenType::ApplePay, + // non_3ds field Applicable to card processing via specific processor using specific currency for pre-approved partner only. + // Equal to 0 by default and 1 for non-3DS transaction, That is why it is hardcoded to 1 for apple pay decrypt flow transactions. + non_3ds: 1, + }))) } } @@ -319,17 +629,35 @@ pub struct PaymentsResponse { pub reference_no: String, #[serde(rename = "TxnID")] pub txn_id: String, - pub txn_type: String, - pub txn_currency: String, - pub txn_amount: String, + pub txn_type: TxnType, + pub txn_currency: Currency, + pub txn_amount: StringMajorUnit, pub txn_channel: String, pub txn_data: TxnData, } #[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] pub struct DuitNowQrCodeResponse { - status: bool, - qrcode_data: Secret, + pub reference_no: String, + pub txn_type: TxnType, + pub txn_currency: Currency, + pub txn_amount: StringMajorUnit, + pub txn_channel: String, + #[serde(rename = "TxnID")] + pub txn_id: String, + pub txn_data: QrTxnData, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct QrTxnData { + pub request_data: QrRequestData, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct QrRequestData { + pub qr_data: Secret, } #[derive(Debug, Serialize, Deserialize)] @@ -338,6 +666,23 @@ pub enum FiuuPaymentsResponse { PaymentResponse(Box), QRPaymentResponse(Box), Error(FiuuErrorResponse), + RecurringResponse(Vec>), +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct FiuuRecurringResponse { + status: FiuuRecurringStautus, + #[serde(rename = "orderid")] + order_id: String, + #[serde(rename = "tranID")] + tran_id: Option, + reason: Option, +} +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "snake_case")] +pub enum FiuuRecurringStautus { + Accepted, + Failed, } #[derive(Debug, Serialize, Deserialize)] @@ -363,15 +708,30 @@ pub enum RequestData { NonThreeDS(NonThreeDSResponseData), RedirectData(Option>), } + +#[derive(Debug, Serialize, Deserialize)] +pub struct QrCodeData { + #[serde(rename = "tranID")] + pub tran_id: String, + pub status: String, +} + #[derive(Debug, Serialize, Deserialize)] pub struct NonThreeDSResponseData { #[serde(rename = "tranID")] pub tran_id: String, pub status: String, + #[serde(rename = "extraP")] + pub extra_parameters: Option, pub error_code: Option, pub error_desc: Option, } +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ExtraParameters { + pub token: Option>, +} + impl TryFrom< ResponseRouterData, @@ -387,16 +747,13 @@ impl >, ) -> Result { match item.response { - FiuuPaymentsResponse::QRPaymentResponse(response) => Ok(Self { - status: match response.status { - false => enums::AttemptStatus::Failure, - true => enums::AttemptStatus::AuthenticationPending, - }, + FiuuPaymentsResponse::QRPaymentResponse(ref response) => Ok(Self { + status: enums::AttemptStatus::AuthenticationPending, response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::NoResponseId, - redirection_data: None, - mandate_reference: None, - connector_metadata: get_qr_metadata(&response)?, + resource_id: ResponseId::ConnectorTransactionId(response.txn_id.clone()), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: get_qr_metadata(response)?, network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, @@ -430,8 +787,8 @@ impl status: enums::AttemptStatus::AuthenticationPending, response: Ok(PaymentsResponseData::TransactionResponse { resource_id: ResponseId::ConnectorTransactionId(data.txn_id), - redirection_data, - mandate_reference: None, + redirection_data: Box::new(redirection_data), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -442,6 +799,18 @@ impl }) } RequestData::NonThreeDS(non_threeds_data) => { + let mandate_reference = + non_threeds_data + .extra_parameters + .as_ref() + .and_then(|extra_p| { + extra_p.token.as_ref().map(|token| MandateReference { + connector_mandate_id: Some(token.clone().expose()), + payment_method_id: None, + mandate_metadata: None, + connector_mandate_request_reference_id: None, + }) + }); let status = match non_threeds_data.status.as_str() { "00" => { if item.data.request.is_auto_capture()? { @@ -474,8 +843,8 @@ impl } else { Ok(PaymentsResponseData::TransactionResponse { resource_id: ResponseId::ConnectorTransactionId(data.txn_id), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(mandate_reference), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -490,6 +859,80 @@ impl }) } }, + FiuuPaymentsResponse::RecurringResponse(ref recurring_response_vec) => { + let recurring_response_item = recurring_response_vec.first(); + let router_data_response = match recurring_response_item { + Some(recurring_response) => { + let status = + common_enums::AttemptStatus::from(recurring_response.status.clone()); + let connector_transaction_id = recurring_response + .tran_id + .as_ref() + .map_or(ResponseId::NoResponseId, |tran_id| { + ResponseId::ConnectorTransactionId(tran_id.clone()) + }); + let response = if status == common_enums::AttemptStatus::Failure { + Err(ErrorResponse { + code: recurring_response + .reason + .clone() + .unwrap_or_else(|| "NO_ERROR_CODE".to_string()), + message: recurring_response + .reason + .clone() + .unwrap_or_else(|| "NO_ERROR_MESSAGE".to_string()), + reason: recurring_response.reason.clone(), + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: recurring_response.tran_id.clone(), + }) + } else { + Ok(PaymentsResponseData::TransactionResponse { + resource_id: connector_transaction_id, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }) + }; + Self { + status, + response, + ..item.data + } + } + None => { + // It is not expected to get empty response from the connnector, if we get we are not updating the payment response since we don't have any info in the authorize response. + let response = Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::NoResponseId, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }); + Self { + response, + ..item.data + } + } + }; + Ok(router_data_response) + } + } + } +} + +impl From for common_enums::AttemptStatus { + fn from(status: FiuuRecurringStautus) -> Self { + match status { + FiuuRecurringStautus::Accepted => Self::Charged, + FiuuRecurringStautus::Failed => Self::Failure, } } } @@ -506,6 +949,8 @@ pub struct FiuuRefundRequest { pub txn_id: String, pub amount: StringMajorUnit, pub signature: Secret, + #[serde(rename = "notify_url")] + pub notify_url: Option, } #[derive(Debug, Serialize, Display)] pub enum RefundType { @@ -534,6 +979,10 @@ impl TryFrom<&FiuuRouterData<&RefundsRouterData>> for FiuuRefundRequest RefundType::Partial, txn_amount.get_amount_as_string() ))?, + notify_url: Some( + Url::parse(&item.router_data.request.get_webhook_url()?) + .change_context(errors::ConnectorError::RequestEncodingFailed)?, + ), }) } } @@ -544,6 +993,8 @@ pub struct FiuuRefundSuccessResponse { #[serde(rename = "RefundID")] refund_id: i64, status: String, + #[serde(rename = "reason")] + reason: Option, } #[derive(Debug, Serialize, Deserialize)] #[serde(untagged)] @@ -570,31 +1021,51 @@ impl TryFrom> }), ..item.data }), - FiuuRefundResponse::Success(refund_data) => Ok(Self { - response: Ok(RefundsResponseData { - connector_refund_id: refund_data.refund_id.to_string(), - refund_status: match refund_data.status.as_str() { - "00" => Ok(enums::RefundStatus::Success), - "11" => Ok(enums::RefundStatus::Failure), - "22" => Ok(enums::RefundStatus::Pending), - other => Err(errors::ConnectorError::UnexpectedResponseError( - bytes::Bytes::from(other.to_owned()), - )), - }?, - }), - ..item.data - }), + FiuuRefundResponse::Success(refund_data) => { + let refund_status = match refund_data.status.as_str() { + "00" => Ok(enums::RefundStatus::Success), + "11" => Ok(enums::RefundStatus::Failure), + "22" => Ok(enums::RefundStatus::Pending), + other => Err(errors::ConnectorError::UnexpectedResponseError( + bytes::Bytes::from(other.to_owned()), + )), + }?; + if refund_status == enums::RefundStatus::Failure { + Ok(Self { + response: Err(ErrorResponse { + code: refund_data.status.clone(), + message: refund_data + .reason + .clone() + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + reason: refund_data.reason.clone(), + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: None, + }), + ..item.data + }) + } else { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: refund_data.refund_id.to_string(), + refund_status, + }), + ..item.data + }) + } + } } } } -#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Serialize, Deserialize)] pub struct FiuuErrorResponse { pub error_code: String, pub error_desc: String, } -#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Serialize, Deserialize)] pub struct FiuuPaymentSyncRequest { amount: StringMajorUnit, #[serde(rename = "txID")] @@ -603,10 +1074,17 @@ pub struct FiuuPaymentSyncRequest { skey: Secret, } -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(untagged)] +pub enum FiuuPaymentResponse { + FiuuPaymentSyncResponse(FiuuPaymentSyncResponse), + FiuuWebhooksPaymentResponse(FiuuWebhooksPaymentResponse), +} + +#[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "PascalCase")] pub struct FiuuPaymentSyncResponse { - stat_code: String, + stat_code: StatCode, stat_name: StatName, #[serde(rename = "TranID")] tran_id: String, @@ -616,6 +1094,16 @@ pub struct FiuuPaymentSyncResponse { miscellaneous: Option>>, } +#[derive(Debug, Serialize, Deserialize, Display, Clone, PartialEq)] +pub enum StatCode { + #[serde(rename = "00")] + Success, + #[serde(rename = "11")] + Failure, + #[serde(rename = "22")] + Pending, +} + #[derive(Debug, Serialize, Deserialize, Display, Clone, Copy, PartialEq)] #[serde(rename_all = "lowercase")] pub enum StatName { @@ -664,57 +1152,137 @@ impl TryFrom<&PaymentsSyncRouterData> for FiuuPaymentSyncRequest { } } -impl TryFrom> for PaymentsSyncRouterData { +impl TryFrom> for PaymentsSyncRouterData { type Error = Report; fn try_from( - item: PaymentsSyncResponseRouterData, + item: PaymentsSyncResponseRouterData, ) -> Result { - let stat_name = item.response.stat_name; - let status = match item.response.stat_code.as_str() { - "00" => { - if stat_name == StatName::Captured || stat_name == StatName::Settled { - Ok(enums::AttemptStatus::Charged) + match item.response { + FiuuPaymentResponse::FiuuPaymentSyncResponse(response) => { + let stat_name = response.stat_name; + let stat_code = response.stat_code.clone(); + let status = enums::AttemptStatus::try_from(FiuuSyncStatus { + stat_name, + stat_code, + })?; + let error_response = if status == enums::AttemptStatus::Failure { + Some(ErrorResponse { + status_code: item.http_code, + code: response.stat_code.to_string(), + message: response.stat_name.clone().to_string(), + reason: Some(response.stat_name.clone().to_string()), + attempt_status: Some(enums::AttemptStatus::Failure), + connector_transaction_id: None, + }) } else { - Ok(enums::AttemptStatus::Authorized) - } + None + }; + let payments_response_data = PaymentsResponseData::TransactionResponse { + resource_id: item.data.request.connector_transaction_id.clone(), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }; + Ok(Self { + status, + response: error_response.map_or_else(|| Ok(payments_response_data), Err), + ..item.data + }) } - "22" => Ok(enums::AttemptStatus::Pending), - "11" => Ok(enums::AttemptStatus::Failure), - other => Err(errors::ConnectorError::UnexpectedResponseError( - bytes::Bytes::from(other.to_owned()), - )), - }?; - let error_response = if status == enums::AttemptStatus::Failure { - Some(ErrorResponse { - status_code: item.http_code, - code: item.response.stat_code.as_str().to_owned(), - message: item.response.stat_name.clone().to_string(), - reason: Some(item.response.stat_name.clone().to_string()), - attempt_status: Some(enums::AttemptStatus::Failure), - connector_transaction_id: None, - }) - } else { - None - }; - let payments_response_data = PaymentsResponseData::TransactionResponse { - resource_id: item.data.request.connector_transaction_id.clone(), - redirection_data: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: None, - incremental_authorization_allowed: None, - charge_id: None, - }; - Ok(Self { - status, - response: error_response.map_or_else(|| Ok(payments_response_data), Err), - ..item.data - }) + FiuuPaymentResponse::FiuuWebhooksPaymentResponse(response) => { + let status = enums::AttemptStatus::try_from(FiuuWebhookStatus { + capture_method: item.data.request.capture_method, + status: response.status, + })?; + let mandate_reference = response.extra_parameters.as_ref().and_then(|extra_p| { + let mandate_token: Result = serde_json::from_str(extra_p); + match mandate_token { + Ok(token) => { + token.token.as_ref().map(|token| MandateReference { + connector_mandate_id: Some(token.clone().expose()), + payment_method_id: None, + mandate_metadata: None, + connector_mandate_request_reference_id:None + }) + } + Err(err) => { + router_env::logger::warn!( + "Failed to convert 'extraP' from fiuu webhook response to fiuu::ExtraParameters. \ + Input: '{}', Error: {}", + extra_p, + err + ); + None + } + } + }); + let error_response = if status == enums::AttemptStatus::Failure { + Some(ErrorResponse { + status_code: item.http_code, + code: response + .error_code + .clone() + .unwrap_or(consts::NO_ERROR_CODE.to_owned()), + message: response + .error_code + .clone() + .unwrap_or(consts::NO_ERROR_MESSAGE.to_owned()), + reason: response.error_desc.clone(), + attempt_status: Some(enums::AttemptStatus::Failure), + connector_transaction_id: None, + }) + } else { + None + }; + let payments_response_data = PaymentsResponseData::TransactionResponse { + resource_id: item.data.request.connector_transaction_id.clone(), + redirection_data: Box::new(None), + mandate_reference: Box::new(mandate_reference), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }; + Ok(Self { + status, + response: error_response.map_or_else(|| Ok(payments_response_data), Err), + ..item.data + }) + } + } } } -#[derive(Debug, Serialize, Deserialize, PartialEq)] +pub struct FiuuWebhookStatus { + pub capture_method: Option, + pub status: FiuuPaymentWebhookStatus, +} + +impl TryFrom for enums::AttemptStatus { + type Error = Report; + fn try_from(webhook_status: FiuuWebhookStatus) -> Result { + match webhook_status.status { + FiuuPaymentWebhookStatus::Success => match webhook_status.capture_method { + Some(CaptureMethod::Automatic) | Some(CaptureMethod::SequentialAutomatic) => { + Ok(Self::Charged) + } + Some(CaptureMethod::Manual) => Ok(Self::Authorized), + _ => Err(errors::ConnectorError::UnexpectedResponseError( + bytes::Bytes::from(webhook_status.status.to_string()), + ))?, + }, + FiuuPaymentWebhookStatus::Failure => Ok(Self::Failure), + FiuuPaymentWebhookStatus::Pending => Ok(Self::AuthenticationPending), + } + } +} + +#[derive(Debug, Serialize, Deserialize)] pub struct PaymentCaptureRequest { domain: String, #[serde(rename = "tranID")] @@ -725,7 +1293,7 @@ pub struct PaymentCaptureRequest { skey: Secret, } -#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct PaymentCaptureResponse { #[serde(rename = "TranID")] @@ -733,6 +1301,30 @@ pub struct PaymentCaptureResponse { stat_code: String, } +pub struct FiuuSyncStatus { + pub stat_name: StatName, + pub stat_code: StatCode, +} + +impl TryFrom for enums::AttemptStatus { + type Error = errors::ConnectorError; + fn try_from(sync_status: FiuuSyncStatus) -> Result { + match (sync_status.stat_code, sync_status.stat_name) { + (StatCode::Success, StatName::Captured | StatName::Settled) => Ok(Self::Charged), // For Success as StatCode we can only expect Captured,Settled and Authorized as StatName. + (StatCode::Success, StatName::Authorized) => Ok(Self::Authorized), + (StatCode::Pending, StatName::Pending) => Ok(Self::AuthenticationPending), // For Pending as StatCode we can only expect Pending and Unknow as StatName. + (StatCode::Pending, StatName::Unknown) => Ok(Self::Pending), + (StatCode::Failure, StatName::Cancelled) | (StatCode::Failure, StatName::ReqCancel) => { + Ok(Self::Voided) + } + (StatCode::Failure, _) => Ok(Self::Failure), + (other, _) => Err(errors::ConnectorError::UnexpectedResponseError( + bytes::Bytes::from(other.to_string()), + )), + } + } +} + impl TryFrom<&FiuuRouterData<&PaymentsCaptureRouterData>> for PaymentCaptureRequest { type Error = Report; fn try_from(item: &FiuuRouterData<&PaymentsCaptureRouterData>) -> Result { @@ -817,8 +1409,8 @@ impl TryFrom> }; let payments_response_data = PaymentsResponseData::TransactionResponse { resource_id: ResponseId::ConnectorTransactionId(item.response.tran_id.to_string()), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -833,7 +1425,7 @@ impl TryFrom> } } -#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Serialize, Deserialize)] pub struct FiuuPaymentCancelRequest { #[serde(rename = "txnID")] txn_id: String, @@ -841,7 +1433,7 @@ pub struct FiuuPaymentCancelRequest { skey: Secret, } -#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct FiuuPaymentCancelResponse { #[serde(rename = "TranID")] @@ -928,8 +1520,8 @@ impl TryFrom> }; let payments_response_data = PaymentsResponseData::TransactionResponse { resource_id: ResponseId::ConnectorTransactionId(item.response.tran_id.to_string()), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -978,6 +1570,7 @@ impl TryFrom<&RefundSyncRouterData> for FiuuRefundSyncRequest { pub enum FiuuRefundSyncResponse { Success(Vec), Error(FiuuErrorResponse), + Webhook(FiuuWebhooksRefundResponse), } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -1032,6 +1625,15 @@ impl TryFrom> ..item.data }) } + FiuuRefundSyncResponse::Webhook(fiuu_webhooks_refund_response) => Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: fiuu_webhooks_refund_response.refund_id, + refund_status: enums::RefundStatus::from( + fiuu_webhooks_refund_response.status.clone(), + ), + }), + ..item.data + }), } } } @@ -1050,7 +1652,7 @@ impl From for enums::RefundStatus { pub fn get_qr_metadata( response: &DuitNowQrCodeResponse, ) -> CustomResult, errors::ConnectorError> { - let image_data = QrImage::new_from_data(response.qrcode_data.peek().clone()) + let image_data = QrImage::new_from_data(response.txn_data.request_data.qr_data.peek().clone()) .change_context(errors::ConnectorError::ResponseHandlingFailed)?; let image_data_url = Url::parse(image_data.data.clone().as_str()).ok(); @@ -1069,3 +1671,134 @@ pub fn get_qr_metadata( Ok(None) } } + +#[derive(Debug, Deserialize, Serialize, Clone)] +#[serde(untagged)] +pub enum FiuuWebhooksResponse { + FiuuWebhookPaymentResponse(FiuuWebhooksPaymentResponse), + FiuuWebhookRefundResponse(FiuuWebhooksRefundResponse), +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct FiuuWebhooksPaymentResponse { + pub skey: Secret, + pub status: FiuuPaymentWebhookStatus, + #[serde(rename = "orderid")] + pub order_id: String, + #[serde(rename = "tranID")] + pub tran_id: String, + pub nbcb: String, + pub amount: StringMajorUnit, + pub currency: String, + pub domain: Secret, + pub appcode: Secret, + pub paydate: String, + pub channel: String, + pub error_desc: Option, + pub error_code: Option, + #[serde(rename = "extraP")] + pub extra_parameters: Option, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +#[serde(rename_all = "PascalCase")] +pub struct FiuuWebhooksRefundResponse { + pub refund_type: FiuuWebhooksRefundType, + #[serde(rename = "MerchantID")] + pub merchant_id: Secret, + #[serde(rename = "RefID")] + pub ref_id: String, + #[serde(rename = "RefundID")] + pub refund_id: String, + #[serde(rename = "TxnID")] + pub txn_id: String, + pub amount: StringMajorUnit, + pub status: FiuuRefundsWebhookStatus, + pub signature: Secret, +} + +#[derive(Debug, Deserialize, Serialize, Clone, strum::Display)] +pub enum FiuuRefundsWebhookStatus { + #[strum(serialize = "00")] + #[serde(rename = "00")] + RefundSuccess, + #[strum(serialize = "11")] + #[serde(rename = "11")] + RefundFailure, + #[strum(serialize = "22")] + #[serde(rename = "22")] + RefundPending, +} + +#[derive(Debug, Deserialize, Serialize, Clone, strum::Display)] +pub enum FiuuWebhooksRefundType { + P, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct FiuuWebhookSignauture { + pub skey: Secret, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct FiuuWebhookResourceId { + pub skey: Secret, +} + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct FiuWebhookEvent { + pub status: FiuuPaymentWebhookStatus, +} + +#[derive(Debug, Deserialize, Serialize, Clone, strum::Display)] +pub enum FiuuPaymentWebhookStatus { + #[strum(serialize = "00")] + #[serde(rename = "00")] + Success, + #[strum(serialize = "11")] + #[serde(rename = "11")] + Failure, + #[strum(serialize = "22")] + #[serde(rename = "22")] + Pending, +} + +impl From for StatCode { + fn from(value: FiuuPaymentWebhookStatus) -> Self { + match value { + FiuuPaymentWebhookStatus::Success => Self::Success, + FiuuPaymentWebhookStatus::Failure => Self::Failure, + FiuuPaymentWebhookStatus::Pending => Self::Pending, + } + } +} + +impl From for api_models::webhooks::IncomingWebhookEvent { + fn from(value: FiuuPaymentWebhookStatus) -> Self { + match value { + FiuuPaymentWebhookStatus::Success => Self::PaymentIntentSuccess, + FiuuPaymentWebhookStatus::Failure => Self::PaymentIntentFailure, + FiuuPaymentWebhookStatus::Pending => Self::PaymentIntentProcessing, + } + } +} + +impl From for api_models::webhooks::IncomingWebhookEvent { + fn from(value: FiuuRefundsWebhookStatus) -> Self { + match value { + FiuuRefundsWebhookStatus::RefundSuccess => Self::RefundSuccess, + FiuuRefundsWebhookStatus::RefundFailure => Self::RefundFailure, + FiuuRefundsWebhookStatus::RefundPending => Self::EventNotSupported, + } + } +} + +impl From for enums::RefundStatus { + fn from(value: FiuuRefundsWebhookStatus) -> Self { + match value { + FiuuRefundsWebhookStatus::RefundFailure => Self::Failure, + FiuuRefundsWebhookStatus::RefundSuccess => Self::Success, + FiuuRefundsWebhookStatus::RefundPending => Self::Pending, + } + } +} diff --git a/crates/router/src/connector/forte.rs b/crates/hyperswitch_connectors/src/connectors/forte.rs similarity index 69% rename from crates/router/src/connector/forte.rs rename to crates/hyperswitch_connectors/src/connectors/forte.rs index 5ffa3a589c31..1068922faa7a 100644 --- a/crates/router/src/connector/forte.rs +++ b/crates/hyperswitch_connectors/src/connectors/forte.rs @@ -1,38 +1,58 @@ pub mod transformers; +use api_models::webhooks::{IncomingWebhookEvent, ObjectReferenceId}; use base64::Engine; +use common_enums::enums; use common_utils::{ - request::RequestContent, + consts::BASE64_ENGINE, + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, types::{AmountConvertor, FloatMajorUnit, FloatMajorUnitForConnector}, }; -use diesel_models::enums; use error_stack::{report, ResultExt}; -use masking::PeekInterface; -use transformers as forte; - -use super::utils::convert_amount; -use crate::{ - configs::settings, - connector::{ - utils as connector_utils, - utils::{PaymentsSyncRequestData, RefundsRequestData}, +use hyperswitch_domain_models::{ + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, }, - consts, - core::errors::{self, CustomResult}, - events::connector_api_logs::ConnectorEvent, - headers, - services::{ - self, - request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, types::{ - self, - api::{self, ConnectorCommon, ConnectorCommonExt}, - ErrorResponse, Response, + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, }, - utils::BytesExt, }; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + consts::NO_ERROR_CODE, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, +}; +use masking::{Mask, PeekInterface}; +use transformers as forte; + +use crate::{ + constants::headers, + types::ResponseRouterData, + utils::{ + construct_not_supported_error_report, convert_amount, PaymentsSyncRequestData, + RefundsRequestData, + }, +}; + #[derive(Clone)] pub struct Forte { amount_converter: &'static (dyn AmountConvertor + Sync), @@ -59,12 +79,8 @@ impl api::RefundExecute for Forte {} impl api::RefundSync for Forte {} impl api::PaymentToken for Forte {} -impl - ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Forte +impl ConnectorIntegration + for Forte { } pub const AUTH_ORG_ID_HEADER: &str = "X-Forte-Auth-Organization-Id"; @@ -75,9 +91,9 @@ where { fn build_headers( &self, - req: &types::RouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let content_type = ConnectorCommon::common_get_content_type(self); let mut common_headers = self.get_auth_header(&req.connector_auth_type)?; common_headers.push(( @@ -97,14 +113,14 @@ impl ConnectorCommon for Forte { "application/json" } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.forte.base_url.as_ref() } fn get_auth_header( &self, - auth_type: &types::ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { let auth = forte::ForteAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; let raw_basic_token = format!( @@ -112,7 +128,7 @@ impl ConnectorCommon for Forte { auth.api_access_id.peek(), auth.api_secret_key.peek() ); - let basic_token = format!("Basic {}", consts::BASE64_ENGINE.encode(raw_basic_token)); + let basic_token = format!("Basic {}", BASE64_ENGINE.encode(raw_basic_token)); Ok(vec![ ( headers::AUTHORIZATION.to_string(), @@ -141,7 +157,7 @@ impl ConnectorCommon for Forte { let code = response .response .response_code - .unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()); + .unwrap_or_else(|| NO_ERROR_CODE.to_string()); Ok(ErrorResponse { status_code: res.status_code, code, @@ -154,47 +170,34 @@ impl ConnectorCommon for Forte { } impl ConnectorValidation for Forte { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( - connector_utils::construct_not_supported_error_report(capture_method, self.id()), + construct_not_supported_error_report(capture_method, self.id()), ), } } } -impl ConnectorIntegration - for Forte -{ -} +impl ConnectorIntegration for Forte {} -impl ConnectorIntegration - for Forte -{ -} +impl ConnectorIntegration for Forte {} -impl - ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Forte -{ +impl ConnectorIntegration for Forte { fn build_request( &self, - _req: &types::RouterData< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - >, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err( errors::ConnectorError::NotImplemented("Setup Mandate flow for Forte".to_string()) .into(), @@ -202,14 +205,12 @@ impl } } -impl ConnectorIntegration - for Forte -{ +impl ConnectorIntegration for Forte { fn get_headers( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -219,8 +220,8 @@ impl ConnectorIntegration CustomResult { let auth: forte::ForteAuthType = forte::ForteAuthType::try_from(&req.connector_auth_type)?; Ok(format!( @@ -233,8 +234,8 @@ impl ConnectorIntegration CustomResult { let amount = convert_amount( self.amount_converter, @@ -249,12 +250,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsAuthorizeType::get_url( self, req, connectors, )?) @@ -271,10 +272,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: forte::FortePaymentsResponse = res .response .parse_struct("Forte AuthorizeResponse") @@ -283,7 +284,7 @@ impl ConnectorIntegration - for Forte -{ +impl ConnectorIntegration for Forte { fn get_headers( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -316,8 +315,8 @@ impl ConnectorIntegration CustomResult { let auth: forte::ForteAuthType = forte::ForteAuthType::try_from(&req.connector_auth_type)?; let txn_id = PaymentsSyncRequestData::get_connector_transaction_id(&req.request) @@ -333,12 +332,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) + RequestBuilder::new() + .method(Method::Get) .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) @@ -347,10 +346,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: forte::FortePaymentsSyncResponse = res .response .parse_struct("forte PaymentsSyncResponse") @@ -359,7 +358,7 @@ impl ConnectorIntegration - for Forte -{ +impl ConnectorIntegration for Forte { fn get_headers( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -392,8 +389,8 @@ impl ConnectorIntegration CustomResult { let auth: forte::ForteAuthType = forte::ForteAuthType::try_from(&req.connector_auth_type)?; Ok(format!( @@ -406,8 +403,8 @@ impl ConnectorIntegration CustomResult { let connector_req = forte::ForteCaptureRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -415,12 +412,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Put) + RequestBuilder::new() + .method(Method::Put) .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsCaptureType::get_headers( @@ -435,10 +432,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: forte::ForteCaptureResponse = res .response .parse_struct("Forte PaymentsCaptureResponse") @@ -447,7 +444,7 @@ impl ConnectorIntegration - for Forte -{ +impl ConnectorIntegration for Forte { fn get_headers( &self, - req: &types::PaymentsCancelRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -479,8 +474,8 @@ impl ConnectorIntegration CustomResult { let auth: forte::ForteAuthType = forte::ForteAuthType::try_from(&req.connector_auth_type)?; Ok(format!( @@ -493,8 +488,8 @@ impl ConnectorIntegration CustomResult { let connector_req = forte::ForteCancelRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -502,12 +497,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Put) + RequestBuilder::new() + .method(Method::Put) .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) @@ -520,10 +515,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: forte::ForteCancelResponse = res .response .parse_struct("forte CancelResponse") @@ -532,7 +527,7 @@ impl ConnectorIntegration for Forte { +impl ConnectorIntegration for Forte { fn get_headers( &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -563,8 +558,8 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, + req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { let auth: forte::ForteAuthType = forte::ForteAuthType::try_from(&req.connector_auth_type)?; Ok(format!( @@ -577,8 +572,8 @@ impl ConnectorIntegration, - _connectors: &settings::Connectors, + req: &RefundsRouterData, + _connectors: &Connectors, ) -> CustomResult { let refund_amount = convert_amount( self.amount_converter, @@ -593,11 +588,11 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) .url(&types::RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundExecuteType::get_headers( @@ -612,10 +607,10 @@ impl ConnectorIntegration, + data: &RefundsRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult, errors::ConnectorError> { let response: forte::RefundResponse = res .response .parse_struct("forte RefundResponse") @@ -624,7 +619,7 @@ impl ConnectorIntegration for Forte { +impl ConnectorIntegration for Forte { fn get_headers( &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -655,8 +650,8 @@ impl ConnectorIntegration CustomResult { let auth: forte::ForteAuthType = forte::ForteAuthType::try_from(&req.connector_auth_type)?; Ok(format!( @@ -670,12 +665,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) + RequestBuilder::new() + .method(Method::Get) .url(&types::RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundSyncType::get_headers(self, req, connectors)?) @@ -685,10 +680,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: forte::RefundSyncResponse = res .response .parse_struct("forte RefundSyncResponse") @@ -697,7 +692,7 @@ impl ConnectorIntegration, - ) -> CustomResult { + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_event_type( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { - Ok(api::IncomingWebhookEvent::EventNotSupported) + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Ok(IncomingWebhookEvent::EventNotSupported) } fn get_webhook_resource_object( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for Forte {} diff --git a/crates/router/src/connector/forte/transformers.rs b/crates/hyperswitch_connectors/src/connectors/forte/transformers.rs similarity index 78% rename from crates/router/src/connector/forte/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/forte/transformers.rs index d9407b410115..2583f47058bf 100644 --- a/crates/router/src/connector/forte/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/forte/transformers.rs @@ -1,14 +1,24 @@ use cards::CardNumber; +use common_enums::enums; use common_utils::types::FloatMajorUnit; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{ConnectorAuthType, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types, +}; +use hyperswitch_interfaces::errors; use masking::{PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use crate::{ - connector::utils::{ - self, AddressDetailsData, CardData, PaymentsAuthorizeRequestData, RouterData, + types::{PaymentsCaptureResponseRouterData, RefundsResponseRouterData, ResponseRouterData}, + utils::{ + self, AddressDetailsData, CardData as _PaymentsAuthorizeRequestData, + PaymentsAuthorizeRequestData, RouterData as _, }, - core::errors, - types::{self, api, domain, storage::enums, transformers::ForeignFrom}, }; #[derive(Debug, Serialize)] @@ -90,7 +100,7 @@ impl TryFrom<&ForteRouterData<&types::PaymentsAuthorizeRouterData>> for FortePay ))? } match item.request.payment_method_data { - domain::PaymentMethodData::Card(ref ccard) => { + PaymentMethodData::Card(ref ccard) => { let action = match item.request.is_auto_capture()? { true => ForteAction::Sale, false => ForteAction::Authorize, @@ -120,22 +130,24 @@ impl TryFrom<&ForteRouterData<&types::PaymentsAuthorizeRouterData>> for FortePay card, }) } - 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::RealTimePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + PaymentMethodData::CardRedirect(_) + | PaymentMethodData::Wallet(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment {} + | PaymentMethodData::Reward {} + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Forte"), ))? @@ -152,11 +164,11 @@ pub struct ForteAuthType { pub(super) api_secret_key: Secret, } -impl TryFrom<&types::ConnectorAuthType> for ForteAuthType { +impl TryFrom<&ConnectorAuthType> for ForteAuthType { type Error = error_stack::Report; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { + fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - types::ConnectorAuthType::MultiAuthKey { + ConnectorAuthType::MultiAuthKey { api_key, key1, api_secret, @@ -195,17 +207,15 @@ impl From for enums::AttemptStatus { } } -impl ForeignFrom<(ForteResponseCode, ForteAction)> for enums::AttemptStatus { - fn foreign_from((response_code, action): (ForteResponseCode, ForteAction)) -> Self { - match response_code { - ForteResponseCode::A01 => match action { - ForteAction::Authorize => Self::Authorized, - ForteAction::Sale => Self::Pending, - ForteAction::Verify => Self::Charged, - }, - ForteResponseCode::A05 | ForteResponseCode::A06 => Self::Authorizing, - _ => Self::Failure, - } +fn get_status(response_code: ForteResponseCode, action: ForteAction) -> enums::AttemptStatus { + match response_code { + ForteResponseCode::A01 => match action { + ForteAction::Authorize => enums::AttemptStatus::Authorized, + ForteAction::Sale => enums::AttemptStatus::Pending, + ForteAction::Verify => enums::AttemptStatus::Charged, + }, + ForteResponseCode::A05 | ForteResponseCode::A06 => enums::AttemptStatus::Authorizing, + _ => enums::AttemptStatus::Failure, } } @@ -276,23 +286,22 @@ pub struct ForteMeta { pub auth_id: String, } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { let response_code = item.response.response.response_code; let action = item.response.action; let transaction_id = &item.response.transaction_id; Ok(Self { - status: enums::AttemptStatus::foreign_from((response_code, action)), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(transaction_id.to_string()), - redirection_data: None, - mandate_reference: None, + status: get_status(response_code, action), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(transaction_id.to_string()), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: Some(serde_json::json!(ForteMeta { auth_id: item.response.authorization_code, })), @@ -322,26 +331,20 @@ pub struct FortePaymentsSyncResponse { pub response: ResponseStatus, } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< - F, - FortePaymentsSyncResponse, - T, - types::PaymentsResponseData, - >, + item: ResponseRouterData, ) -> Result { let transaction_id = &item.response.transaction_id; Ok(Self { status: enums::AttemptStatus::from(item.response.status), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(transaction_id.to_string()), - redirection_data: None, - mandate_reference: None, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(transaction_id.to_string()), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: Some(serde_json::json!(ForteMeta { auth_id: item.response.authorization_code, })), @@ -397,20 +400,20 @@ pub struct ForteCaptureResponse { pub response: CaptureResponseStatus, } -impl TryFrom> +impl TryFrom> for types::PaymentsCaptureRouterData { type Error = error_stack::Report; fn try_from( - item: types::PaymentsCaptureResponseRouterData, + item: PaymentsCaptureResponseRouterData, ) -> Result { let transaction_id = &item.response.transaction_id; Ok(Self { status: enums::AttemptStatus::from(item.response.response.response_code), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(transaction_id.clone()), - redirection_data: None, - mandate_reference: None, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(transaction_id.clone()), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: Some(serde_json::json!(ForteMeta { auth_id: item.response.authorization_code, })), @@ -465,21 +468,20 @@ pub struct ForteCancelResponse { pub response: CancelResponseStatus, } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { let transaction_id = &item.response.transaction_id; Ok(Self { status: enums::AttemptStatus::from(item.response.response.response_code), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(transaction_id.to_string()), - redirection_data: None, - mandate_reference: None, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(transaction_id.to_string()), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: Some(serde_json::json!(ForteMeta { auth_id: item.response.authorization_code, })), @@ -560,15 +562,15 @@ pub struct RefundResponse { pub response: ResponseStatus, } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item.response.transaction_id, refund_status: enums::RefundStatus::from(item.response.response.response_code), }), @@ -583,15 +585,15 @@ pub struct RefundSyncResponse { transaction_id: String, } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item.response.transaction_id, refund_status: enums::RefundStatus::from(item.response.status), }), diff --git a/crates/hyperswitch_connectors/src/connectors/globepay.rs b/crates/hyperswitch_connectors/src/connectors/globepay.rs index 8faf4190e938..f11b7b5977f2 100644 --- a/crates/hyperswitch_connectors/src/connectors/globepay.rs +++ b/crates/hyperswitch_connectors/src/connectors/globepay.rs @@ -28,7 +28,10 @@ use hyperswitch_domain_models::{ }, }; use hyperswitch_interfaces::{ - api::{self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorValidation}, + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, configs::Connectors, consts, errors, events::connector_api_logs::ConnectorEvent, @@ -195,7 +198,11 @@ impl ConnectorIntegration CustomResult { let query_params = get_globlepay_query_params(&req.connector_auth_type)?; - if req.request.capture_method == Some(common_enums::enums::CaptureMethod::Automatic) { + if matches!( + req.request.capture_method, + Some(common_enums::enums::CaptureMethod::Automatic) + | Some(common_enums::enums::CaptureMethod::SequentialAutomatic) + ) { Ok(format!( "{}api/v1.0/gateway/partners/{}/orders/{}{query_params}", self.base_url(connectors), @@ -549,3 +556,5 @@ impl webhooks::IncomingWebhook for Globepay { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for Globepay {} diff --git a/crates/hyperswitch_connectors/src/connectors/globepay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/globepay/transformers.rs index 4986e8d5f32e..5d30146293cb 100644 --- a/crates/hyperswitch_connectors/src/connectors/globepay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/globepay/transformers.rs @@ -74,6 +74,7 @@ impl TryFrom<&GlobepayRouterData<&types::PaymentsAuthorizeRouterData>> for Globe | WalletData::MobilePayRedirect(_) | WalletData::PaypalRedirect(_) | WalletData::PaypalSdk(_) + | WalletData::Paze(_) | WalletData::SamsungPay(_) | WalletData::TwintRedirect {} | WalletData::VippsRedirect {} @@ -95,14 +96,18 @@ impl TryFrom<&GlobepayRouterData<&types::PaymentsAuthorizeRouterData>> for Globe | PaymentMethodData::MandatePayment | PaymentMethodData::Reward | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) | PaymentMethodData::Upi(_) | PaymentMethodData::Voucher(_) | PaymentMethodData::GiftCard(_) | PaymentMethodData::OpenBanking(_) | PaymentMethodData::CardToken(_) - | PaymentMethodData::NetworkToken(_) => Err(errors::ConnectorError::NotImplemented( - get_unimplemented_payment_method_error_message("globepay"), - ))?, + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + Err(errors::ConnectorError::NotImplemented( + get_unimplemented_payment_method_error_message("globepay"), + ))? + } }; let description = item.get_description()?; Ok(Self { @@ -209,8 +214,8 @@ impl TryFrom TryFrom, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let mut header = vec![ ( headers::CONTENT_TYPE.to_string(), @@ -86,14 +110,14 @@ impl ConnectorCommon for Gocardless { "application/json" } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.gocardless.base_url.as_ref() } fn get_auth_header( &self, - auth_type: &types::ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { let auth = gocardless::GocardlessAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; Ok(vec![( @@ -134,33 +158,29 @@ impl ConnectorCommon for Gocardless { } } -impl - ConnectorIntegration< - api::CreateConnectorCustomer, - types::ConnectorCustomerData, - types::PaymentsResponseData, - > for Gocardless +impl ConnectorIntegration + for Gocardless { fn get_headers( &self, - req: &types::ConnectorCustomerRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &ConnectorCustomerRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } fn get_url( &self, - _req: &types::ConnectorCustomerRouterData, - connectors: &settings::Connectors, + _req: &ConnectorCustomerRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!("{}/customers", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::ConnectorCustomerRouterData, - _connectors: &settings::Connectors, + req: &ConnectorCustomerRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_req = gocardless::GocardlessCustomerRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -168,12 +188,12 @@ impl fn build_request( &self, - req: &types::ConnectorCustomerRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &ConnectorCustomerRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::ConnectorCustomerType::get_url( self, req, connectors, )?) @@ -190,21 +210,17 @@ impl fn handle_response( &self, - data: &types::ConnectorCustomerRouterData, + data: &ConnectorCustomerRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult< - types::RouterData< - api::CreateConnectorCustomer, - types::ConnectorCustomerData, - types::PaymentsResponseData, - >, + RouterData, errors::ConnectorError, > where - api::CreateConnectorCustomer: Clone, - types::ConnectorCustomerData: Clone, - types::PaymentsResponseData: Clone, + CreateConnectorCustomer: Clone, + ConnectorCustomerData: Clone, + PaymentsResponseData: Clone, { let response: gocardless::GocardlessCustomerResponse = res .response @@ -214,7 +230,7 @@ impl event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -230,25 +246,21 @@ impl } } -impl - ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Gocardless +impl ConnectorIntegration + for Gocardless { fn get_headers( &self, - req: &types::TokenizationRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &TokenizationRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } fn get_url( &self, - _req: &types::TokenizationRouterData, - connectors: &settings::Connectors, + _req: &TokenizationRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!( "{}/customer_bank_accounts", @@ -258,8 +270,8 @@ impl fn get_request_body( &self, - req: &types::TokenizationRouterData, - _connectors: &settings::Connectors, + req: &TokenizationRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_req = gocardless::GocardlessBankAccountRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -267,12 +279,12 @@ impl fn build_request( &self, - req: &types::TokenizationRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &TokenizationRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::TokenizationType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::TokenizationType::get_headers(self, req, connectors)?) @@ -285,14 +297,14 @@ impl fn handle_response( &self, - data: &types::TokenizationRouterData, + data: &TokenizationRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult + ) -> CustomResult where - api::PaymentMethodToken: Clone, - types::PaymentMethodTokenizationData: Clone, - types::PaymentsResponseData: Clone, + PaymentMethodToken: Clone, + PaymentMethodTokenizationData: Clone, + PaymentsResponseData: Clone, { let response: gocardless::GocardlessBankAccountResponse = res .response @@ -302,7 +314,7 @@ impl event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -318,35 +330,33 @@ impl } } -impl - ConnectorIntegration< - api::PreProcessing, - types::PaymentsPreProcessingData, - types::PaymentsResponseData, - > for Gocardless +impl ConnectorIntegration + for Gocardless { } impl ConnectorValidation for Gocardless { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic => Ok(()), + enums::CaptureMethod::Automatic | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::Manual | enums::CaptureMethod::ManualMultiple - | enums::CaptureMethod::Scheduled => Err( - connector_utils::construct_not_implemented_error_report(capture_method, self.id()), - ), + | enums::CaptureMethod::Scheduled => Err(construct_not_implemented_error_report( + capture_method, + self.id(), + )), } } fn validate_mandate_payment( &self, - pm_type: Option, - pm_data: types::domain::payments::PaymentMethodData, + pm_type: Option, + pm_data: PaymentMethodData, ) -> CustomResult<(), errors::ConnectorError> { let mandate_supported_pmd = std::collections::HashSet::from([ PaymentMethodDataType::SepaBankDebit, @@ -354,48 +364,39 @@ impl ConnectorValidation for Gocardless { PaymentMethodDataType::BecsBankDebit, PaymentMethodDataType::BacsBankDebit, ]); - connector_utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) + is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) } } -impl ConnectorIntegration - for Gocardless -{ +impl ConnectorIntegration for Gocardless { //TODO: implement sessions flow } -impl ConnectorIntegration - for Gocardless -{ -} +impl ConnectorIntegration for Gocardless {} -impl - ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Gocardless +impl ConnectorIntegration + for Gocardless { fn get_headers( &self, - req: &types::SetupMandateRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &SetupMandateRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } fn get_url( &self, - _req: &types::SetupMandateRouterData, - connectors: &settings::Connectors, + _req: &SetupMandateRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!("{}/mandates", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::SetupMandateRouterData, - _connectors: &settings::Connectors, + req: &SetupMandateRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_req = gocardless::GocardlessMandateRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -403,14 +404,14 @@ impl fn build_request( &self, - req: &types::SetupMandateRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &SetupMandateRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { // Preprocessing flow is to create mandate, which should to be called only in case of First mandate if req.request.setup_mandate_details.is_some() { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::SetupMandateType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::SetupMandateType::get_headers(self, req, connectors)?) @@ -426,10 +427,10 @@ impl fn handle_response( &self, - data: &types::SetupMandateRouterData, + data: &SetupMandateRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: gocardless::GocardlessMandateResponse = res .response .parse_struct("GocardlessMandateResponse") @@ -438,7 +439,7 @@ impl event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -454,14 +455,12 @@ impl } } -impl ConnectorIntegration - for Gocardless -{ +impl ConnectorIntegration for Gocardless { fn get_headers( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -471,16 +470,16 @@ impl ConnectorIntegration CustomResult { Ok(format!("{}/payments", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::PaymentsAuthorizeRouterData, - _connectors: &settings::Connectors, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_router_data = gocardless::GocardlessRouterData::try_from(( &self.get_currency_unit(), @@ -495,12 +494,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsAuthorizeType::get_url( self, req, connectors, )?) @@ -517,10 +516,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: gocardless::GocardlessPaymentsResponse = res .response .parse_struct("GocardlessPaymentsResponse") @@ -529,7 +528,7 @@ impl ConnectorIntegration - for Gocardless -{ +impl ConnectorIntegration for Gocardless { fn get_headers( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -562,8 +559,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}/payments/{}", @@ -577,25 +574,25 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) - .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Get) + .url(&PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .headers(PaymentsSyncType::get_headers(self, req, connectors)?) .build(), )) } fn handle_response( &self, - data: &types::PaymentsSyncRouterData, + data: &PaymentsSyncRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: gocardless::GocardlessPaymentsResponse = res .response .parse_struct("GocardlessPaymentsResponse") @@ -604,7 +601,7 @@ impl ConnectorIntegration - for Gocardless -{ -} +impl ConnectorIntegration for Gocardless {} -impl ConnectorIntegration - for Gocardless -{ -} +impl ConnectorIntegration for Gocardless {} -impl ConnectorIntegration - for Gocardless -{ +impl ConnectorIntegration for Gocardless { fn get_headers( &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -647,16 +636,16 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, + _req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!("{}/refunds", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::RefundsRouterData, - _connectors: &settings::Connectors, + req: &RefundsRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_router_data = gocardless::GocardlessRouterData::try_from(( &self.get_currency_unit(), @@ -670,11 +659,11 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) .url(&types::RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundExecuteType::get_headers( @@ -689,10 +678,10 @@ impl ConnectorIntegration, + data: &RefundsRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult, errors::ConnectorError> { let response: gocardless::RefundResponse = res .response .parse_struct("gocardless RefundResponse") @@ -701,7 +690,7 @@ impl ConnectorIntegration - for Gocardless -{ +impl ConnectorIntegration for Gocardless { fn build_request( &self, - _req: &types::RefundSyncRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &RefundSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(None) } } #[async_trait::async_trait] -impl api::IncomingWebhook for Gocardless { +impl IncomingWebhook for Gocardless { fn get_webhook_source_verification_algorithm( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Ok(Box::new(crypto::HmacSha256)) } fn get_webhook_source_verification_signature( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { let signature = request @@ -759,7 +746,7 @@ impl api::IncomingWebhook for Gocardless { fn get_webhook_source_verification_message( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, _merchant_id: &common_utils::id_type::MerchantId, _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { @@ -770,8 +757,8 @@ impl api::IncomingWebhook for Gocardless { fn get_webhook_object_reference_id( &self, - request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { let details: gocardless::GocardlessWebhookEvent = request .body .parse_struct("GocardlessWebhookEvent") @@ -785,18 +772,18 @@ impl api::IncomingWebhook for Gocardless { let payment_id = api_models::payments::PaymentIdType::ConnectorTransactionId( link.payment.to_owned(), ); - api::webhooks::ObjectReferenceId::PaymentId(payment_id) + ObjectReferenceId::PaymentId(payment_id) } transformers::WebhooksLink::RefundWebhookLink(link) => { let refund_id = api_models::webhooks::RefundIdType::ConnectorRefundId(link.refund.to_owned()); - api::webhooks::ObjectReferenceId::RefundId(refund_id) + ObjectReferenceId::RefundId(refund_id) } transformers::WebhooksLink::MandateWebhookLink(link) => { let mandate_id = api_models::webhooks::MandateIdType::ConnectorMandateId( link.mandate.to_owned(), ); - api::webhooks::ObjectReferenceId::MandateId(mandate_id) + ObjectReferenceId::MandateId(mandate_id) } }; Ok(reference_id) @@ -804,8 +791,8 @@ impl api::IncomingWebhook for Gocardless { fn get_webhook_event_type( &self, - request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { let details: gocardless::GocardlessWebhookEvent = request .body .parse_struct("GocardlessWebhookEvent") @@ -819,41 +806,37 @@ impl api::IncomingWebhook for Gocardless { transformers::PaymentsAction::Created | transformers::PaymentsAction::Submitted | transformers::PaymentsAction::CustomerApprovalGranted => { - api::IncomingWebhookEvent::PaymentIntentProcessing + IncomingWebhookEvent::PaymentIntentProcessing } transformers::PaymentsAction::CustomerApprovalDenied | transformers::PaymentsAction::Failed | transformers::PaymentsAction::Cancelled | transformers::PaymentsAction::LateFailureSettled => { - api::IncomingWebhookEvent::PaymentIntentFailure + IncomingWebhookEvent::PaymentIntentFailure } transformers::PaymentsAction::Confirmed | transformers::PaymentsAction::PaidOut => { - api::IncomingWebhookEvent::PaymentIntentSuccess + IncomingWebhookEvent::PaymentIntentSuccess } transformers::PaymentsAction::SurchargeFeeDebited | transformers::PaymentsAction::ResubmissionRequired => { - api::IncomingWebhookEvent::EventNotSupported + IncomingWebhookEvent::EventNotSupported } }, transformers::WebhookAction::RefundsAction(action) => match action { - transformers::RefundsAction::Failed => api::IncomingWebhookEvent::RefundFailure, - transformers::RefundsAction::Paid => api::IncomingWebhookEvent::RefundSuccess, + transformers::RefundsAction::Failed => IncomingWebhookEvent::RefundFailure, + transformers::RefundsAction::Paid => IncomingWebhookEvent::RefundSuccess, transformers::RefundsAction::RefundSettled | transformers::RefundsAction::FundsReturned - | transformers::RefundsAction::Created => { - api::IncomingWebhookEvent::EventNotSupported - } + | transformers::RefundsAction::Created => IncomingWebhookEvent::EventNotSupported, }, transformers::WebhookAction::MandatesAction(action) => match action { transformers::MandatesAction::Active | transformers::MandatesAction::Reinstated => { - api::IncomingWebhookEvent::MandateActive + IncomingWebhookEvent::MandateActive } transformers::MandatesAction::Expired | transformers::MandatesAction::Cancelled | transformers::MandatesAction::Failed - | transformers::MandatesAction::Consumed => { - api::IncomingWebhookEvent::MandateRevoked - } + | transformers::MandatesAction::Consumed => IncomingWebhookEvent::MandateRevoked, transformers::MandatesAction::Created | transformers::MandatesAction::CustomerApprovalGranted | transformers::MandatesAction::CustomerApprovalSkipped @@ -861,9 +844,7 @@ impl api::IncomingWebhook for Gocardless { | transformers::MandatesAction::Submitted | transformers::MandatesAction::ResubmissionRequested | transformers::MandatesAction::Replaced - | transformers::MandatesAction::Blocked => { - api::IncomingWebhookEvent::EventNotSupported - } + | transformers::MandatesAction::Blocked => IncomingWebhookEvent::EventNotSupported, }, }; Ok(event_type) @@ -871,7 +852,7 @@ impl api::IncomingWebhook for Gocardless { fn get_webhook_resource_object( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { let details: gocardless::GocardlessWebhookEvent = request .body @@ -891,3 +872,5 @@ impl api::IncomingWebhook for Gocardless { } } } + +impl ConnectorSpecifications for Gocardless {} diff --git a/crates/router/src/connector/gocardless/transformers.rs b/crates/hyperswitch_connectors/src/connectors/gocardless/transformers.rs similarity index 78% rename from crates/router/src/connector/gocardless/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/gocardless/transformers.rs index ff2f9d95026e..d64c2ed8efde 100644 --- a/crates/router/src/connector/gocardless/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/gocardless/transformers.rs @@ -1,23 +1,29 @@ -use api_models::{ - enums::{CountryAlpha2, UsStatesAbbreviation}, - payments::AddressDetails, -}; +use common_enums::{enums, CountryAlpha2, UsStatesAbbreviation}; use common_utils::{ id_type, pii::{self, IpAddress}, }; +use hyperswitch_domain_models::{ + address::AddressDetails, + payment_method_data::{BankDebitData, PaymentMethodData}, + router_data::{ConnectorAuthType, PaymentMethodToken, RouterData}, + router_flow_types::refunds::Execute, + router_request_types::{ + ConnectorCustomerData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsSyncData, ResponseId, SetupMandateRequestData, + }, + router_response_types::{MandateReference, PaymentsResponseData, RefundsResponseData}, + types, +}; +use hyperswitch_interfaces::{api, errors}; use masking::{ExposeInterface, Secret}; use serde::{Deserialize, Serialize}; use crate::{ - connector::utils::{ - self, AddressDetailsData, BrowserInformationData, ConnectorCustomerData, - PaymentsAuthorizeRequestData, PaymentsSetupMandateRequestData, RouterData, - }, - core::errors, - types::{ - self, api, domain, storage::enums, transformers::ForeignTryFrom, MandateReference, - ResponseId, + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::{ + self, AddressDetailsData, BrowserInformationData, CustomerData, ForeignTryFrom, + PaymentsAuthorizeRequestData, PaymentsSetupMandateRequestData, RouterData as _, }, }; @@ -130,25 +136,25 @@ pub struct Customers { impl TryFrom< - types::ResponseRouterData< + ResponseRouterData< F, GocardlessCustomerResponse, - types::ConnectorCustomerData, - types::PaymentsResponseData, + ConnectorCustomerData, + PaymentsResponseData, >, - > for types::RouterData + > for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< + item: ResponseRouterData< F, GocardlessCustomerResponse, - types::ConnectorCustomerData, - types::PaymentsResponseData, + ConnectorCustomerData, + PaymentsResponseData, >, ) -> Result { Ok(Self { - response: Ok(types::PaymentsResponseData::ConnectorCustomerResponse { + response: Ok(PaymentsResponseData::ConnectorCustomerResponse { connector_customer_id: item.response.customers.id.expose(), }), ..item.data @@ -230,25 +236,27 @@ impl TryFrom<&types::TokenizationRouterData> for CustomerBankAccount { type Error = error_stack::Report; fn try_from(item: &types::TokenizationRouterData) -> Result { match &item.request.payment_method_data { - domain::PaymentMethodData::BankDebit(bank_debit_data) => { + PaymentMethodData::BankDebit(bank_debit_data) => { Self::try_from((bank_debit_data, item)) } - domain::PaymentMethodData::Card(_) - | domain::PaymentMethodData::CardRedirect(_) - | domain::PaymentMethodData::Wallet(_) - | domain::PaymentMethodData::PayLater(_) - | domain::PaymentMethodData::BankRedirect(_) - | domain::PaymentMethodData::BankTransfer(_) - | domain::PaymentMethodData::Crypto(_) - | domain::PaymentMethodData::MandatePayment - | domain::PaymentMethodData::Reward - | domain::PaymentMethodData::RealTimePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + PaymentMethodData::Card(_) + | PaymentMethodData::CardRedirect(_) + | PaymentMethodData::Wallet(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Gocardless"), ) @@ -258,13 +266,13 @@ impl TryFrom<&types::TokenizationRouterData> for CustomerBankAccount { } } -impl TryFrom<(&domain::BankDebitData, &types::TokenizationRouterData)> for CustomerBankAccount { +impl TryFrom<(&BankDebitData, &types::TokenizationRouterData)> for CustomerBankAccount { type Error = error_stack::Report; fn try_from( - (bank_debit_data, item): (&domain::BankDebitData, &types::TokenizationRouterData), + (bank_debit_data, item): (&BankDebitData, &types::TokenizationRouterData), ) -> Result { match bank_debit_data { - domain::BankDebitData::AchBankDebit { + BankDebitData::AchBankDebit { account_number, routing_number, bank_type, @@ -282,7 +290,7 @@ impl TryFrom<(&domain::BankDebitData, &types::TokenizationRouterData)> for Custo }; Ok(Self::USBankAccount(us_bank_account)) } - domain::BankDebitData::BecsBankDebit { + BankDebitData::BecsBankDebit { account_number, bsb_number, .. @@ -297,7 +305,7 @@ impl TryFrom<(&domain::BankDebitData, &types::TokenizationRouterData)> for Custo }; Ok(Self::AUBankAccount(au_bank_account)) } - domain::BankDebitData::SepaBankDebit { iban, .. } => { + BankDebitData::SepaBankDebit { iban, .. } => { let account_holder_name = item.get_billing_full_name()?; let international_bank_account = InternationalBankAccount { iban: iban.clone(), @@ -305,12 +313,10 @@ impl TryFrom<(&domain::BankDebitData, &types::TokenizationRouterData)> for Custo }; Ok(Self::InternationalBankAccount(international_bank_account)) } - domain::BankDebitData::BacsBankDebit { .. } => { - Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("Gocardless"), - ) - .into()) - } + BankDebitData::BacsBankDebit { .. } => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Gocardless"), + ) + .into()), } } } @@ -336,25 +342,25 @@ pub struct CustomerBankAccountResponse { impl TryFrom< - types::ResponseRouterData< + ResponseRouterData< F, GocardlessBankAccountResponse, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, + PaymentMethodTokenizationData, + PaymentsResponseData, >, - > for types::RouterData + > for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< + item: ResponseRouterData< F, GocardlessBankAccountResponse, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, + PaymentMethodTokenizationData, + PaymentsResponseData, >, ) -> Result { Ok(Self { - response: Ok(types::PaymentsResponseData::TokenizationResponse { + response: Ok(PaymentsResponseData::TokenizationResponse { token: item.response.customer_bank_accounts.id.expose(), }), ..item.data @@ -398,29 +404,31 @@ impl TryFrom<&types::SetupMandateRouterData> for GocardlessMandateRequest { type Error = error_stack::Report; fn try_from(item: &types::SetupMandateRouterData) -> Result { let (scheme, payer_ip_address) = match &item.request.payment_method_data { - domain::PaymentMethodData::BankDebit(bank_debit_data) => { + PaymentMethodData::BankDebit(bank_debit_data) => { let payer_ip_address = get_ip_if_required(bank_debit_data, item)?; Ok(( GocardlessScheme::try_from(bank_debit_data)?, payer_ip_address, )) } - domain::PaymentMethodData::Card(_) - | domain::PaymentMethodData::CardRedirect(_) - | domain::PaymentMethodData::Wallet(_) - | domain::PaymentMethodData::PayLater(_) - | domain::PaymentMethodData::BankRedirect(_) - | domain::PaymentMethodData::BankTransfer(_) - | domain::PaymentMethodData::Crypto(_) - | domain::PaymentMethodData::MandatePayment - | domain::PaymentMethodData::Reward - | domain::PaymentMethodData::RealTimePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + PaymentMethodData::Card(_) + | PaymentMethodData::CardRedirect(_) + | PaymentMethodData::Wallet(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( "Setup Mandate flow for selected payment method through Gocardless".to_string(), )) @@ -428,8 +436,8 @@ impl TryFrom<&types::SetupMandateRouterData> for GocardlessMandateRequest { }?; let payment_method_token = item.get_payment_method_token()?; let customer_bank_account = match payment_method_token { - types::PaymentMethodToken::Token(token) => Ok(token), - types::PaymentMethodToken::ApplePayDecrypt(_) => { + PaymentMethodToken::Token(token) => Ok(token), + PaymentMethodToken::ApplePayDecrypt(_) | PaymentMethodToken::PazeDecrypt(_) => { Err(errors::ConnectorError::NotImplemented( "Setup Mandate flow for selected payment method through Gocardless".to_string(), )) @@ -451,31 +459,29 @@ impl TryFrom<&types::SetupMandateRouterData> for GocardlessMandateRequest { } fn get_ip_if_required( - bank_debit_data: &domain::BankDebitData, + bank_debit_data: &BankDebitData, item: &types::SetupMandateRouterData, ) -> Result>, error_stack::Report> { let ip_address = item.request.get_browser_info()?.get_ip_address()?; match bank_debit_data { - domain::BankDebitData::AchBankDebit { .. } => Ok(Some(ip_address)), - domain::BankDebitData::SepaBankDebit { .. } - | domain::BankDebitData::BecsBankDebit { .. } - | domain::BankDebitData::BacsBankDebit { .. } => Ok(None), + BankDebitData::AchBankDebit { .. } => Ok(Some(ip_address)), + BankDebitData::SepaBankDebit { .. } + | BankDebitData::BecsBankDebit { .. } + | BankDebitData::BacsBankDebit { .. } => Ok(None), } } -impl TryFrom<&domain::BankDebitData> for GocardlessScheme { +impl TryFrom<&BankDebitData> for GocardlessScheme { type Error = error_stack::Report; - fn try_from(item: &domain::BankDebitData) -> Result { + fn try_from(item: &BankDebitData) -> Result { match item { - domain::BankDebitData::AchBankDebit { .. } => Ok(Self::Ach), - domain::BankDebitData::SepaBankDebit { .. } => Ok(Self::SepaCore), - domain::BankDebitData::BecsBankDebit { .. } => Ok(Self::Becs), - domain::BankDebitData::BacsBankDebit { .. } => { - Err(errors::ConnectorError::NotImplemented( - "Setup Mandate flow for selected payment method through Gocardless".to_string(), - ) - .into()) - } + BankDebitData::AchBankDebit { .. } => Ok(Self::Ach), + BankDebitData::SepaBankDebit { .. } => Ok(Self::SepaCore), + BankDebitData::BecsBankDebit { .. } => Ok(Self::Becs), + BankDebitData::BacsBankDebit { .. } => Err(errors::ConnectorError::NotImplemented( + "Setup Mandate flow for selected payment method through Gocardless".to_string(), + ) + .into()), } } } @@ -492,36 +498,37 @@ pub struct MandateResponse { impl TryFrom< - types::ResponseRouterData< + ResponseRouterData< F, GocardlessMandateResponse, - types::SetupMandateRequestData, - types::PaymentsResponseData, + SetupMandateRequestData, + PaymentsResponseData, >, - > for types::RouterData + > for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< + item: ResponseRouterData< F, GocardlessMandateResponse, - types::SetupMandateRequestData, - types::PaymentsResponseData, + SetupMandateRequestData, + PaymentsResponseData, >, ) -> Result { let mandate_reference = Some(MandateReference { connector_mandate_id: Some(item.response.mandates.id.clone().expose()), payment_method_id: None, mandate_metadata: None, + connector_mandate_request_reference_id: None, }); Ok(Self { - response: Ok(types::PaymentsResponseData::TransactionResponse { + response: Ok(PaymentsResponseData::TransactionResponse { connector_metadata: None, connector_response_reference_id: None, incremental_authorization_allowed: None, resource_id: ResponseId::NoResponseId, - redirection_data: None, - mandate_reference, + redirection_data: Box::new(None), + mandate_reference: Box::new(mandate_reference), network_txn_id: None, charge_id: None, }), @@ -593,11 +600,11 @@ pub struct GocardlessAuthType { pub(super) access_token: Secret, } -impl TryFrom<&types::ConnectorAuthType> for GocardlessAuthType { +impl TryFrom<&ConnectorAuthType> for GocardlessAuthType { type Error = error_stack::Report; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { + fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - types::ConnectorAuthType::HeaderKey { api_key } => Ok(Self { + ConnectorAuthType::HeaderKey { api_key } => Ok(Self { access_token: api_key.to_owned(), }), _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), @@ -645,34 +652,35 @@ pub struct PaymentResponse { impl TryFrom< - types::ResponseRouterData< + ResponseRouterData< F, GocardlessPaymentsResponse, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, + PaymentsAuthorizeData, + PaymentsResponseData, >, - > for types::RouterData + > for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< + item: ResponseRouterData< F, GocardlessPaymentsResponse, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, + PaymentsAuthorizeData, + PaymentsResponseData, >, ) -> Result { let mandate_reference = MandateReference { connector_mandate_id: Some(item.data.request.get_connector_mandate_id()?), payment_method_id: None, mandate_metadata: None, + connector_mandate_request_reference_id: None, }; Ok(Self { status: enums::AttemptStatus::from(item.response.payments.status), - response: Ok(types::PaymentsResponseData::TransactionResponse { + response: Ok(PaymentsResponseData::TransactionResponse { resource_id: ResponseId::ConnectorTransactionId(item.response.payments.id), - redirection_data: None, - mandate_reference: Some(mandate_reference), + redirection_data: Box::new(None), + mandate_reference: Box::new(Some(mandate_reference)), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -686,29 +694,24 @@ impl impl TryFrom< - types::ResponseRouterData< - F, - GocardlessPaymentsResponse, - types::PaymentsSyncData, - types::PaymentsResponseData, - >, - > for types::RouterData + ResponseRouterData, + > for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< + item: ResponseRouterData< F, GocardlessPaymentsResponse, - types::PaymentsSyncData, - types::PaymentsResponseData, + PaymentsSyncData, + PaymentsResponseData, >, ) -> Result { Ok(Self { status: enums::AttemptStatus::from(item.response.payments.status), - response: Ok(types::PaymentsResponseData::TransactionResponse { + response: Ok(PaymentsResponseData::TransactionResponse { resource_id: ResponseId::ConnectorTransactionId(item.response.payments.id), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -767,15 +770,15 @@ pub struct RefundResponse { id: String, } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item.response.id.to_string(), refund_status: enums::RefundStatus::Pending, }), diff --git a/crates/hyperswitch_connectors/src/connectors/helcim.rs b/crates/hyperswitch_connectors/src/connectors/helcim.rs index 8d632d8b7e85..5f0e49d84e15 100644 --- a/crates/hyperswitch_connectors/src/connectors/helcim.rs +++ b/crates/hyperswitch_connectors/src/connectors/helcim.rs @@ -29,7 +29,10 @@ use hyperswitch_domain_models::{ }, }; use hyperswitch_interfaces::{ - api::{self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorValidation}, + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, configs::Connectors, consts::NO_ERROR_CODE, errors, @@ -65,9 +68,9 @@ impl api::PaymentToken for Helcim {} impl Helcim { pub fn connector_transaction_id( &self, - connector_meta: &Option, + connector_meta: Option<&serde_json::Value>, ) -> CustomResult, errors::ConnectorError> { - let meta: helcim::HelcimMetaData = to_connector_meta(connector_meta.clone())?; + let meta: helcim::HelcimMetaData = to_connector_meta(connector_meta.cloned())?; Ok(Some(meta.preauth_transaction_id.to_string())) } } @@ -170,14 +173,17 @@ impl ConnectorCommon for Helcim { } impl ConnectorValidation for Helcim { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( crate::utils::construct_not_supported_error_report(capture_method, self.id()), ), @@ -808,3 +814,5 @@ impl IncomingWebhook for Helcim { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for Helcim {} diff --git a/crates/hyperswitch_connectors/src/connectors/helcim/transformers.rs b/crates/hyperswitch_connectors/src/connectors/helcim/transformers.rs index 90380d023215..4a8ddf68df9a 100644 --- a/crates/hyperswitch_connectors/src/connectors/helcim/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/helcim/transformers.rs @@ -178,12 +178,16 @@ impl TryFrom<&SetupMandateRouterData> for HelcimVerifyRequest { | PaymentMethodData::RealTimePayment(_) | PaymentMethodData::Upi(_) | PaymentMethodData::Voucher(_) + | PaymentMethodData::MobilePayment(_) | PaymentMethodData::GiftCard(_) | PaymentMethodData::OpenBanking(_) | PaymentMethodData::CardToken(_) - | PaymentMethodData::NetworkToken(_) => Err(errors::ConnectorError::NotImplemented( - crate::utils::get_unimplemented_payment_method_error_message("Helcim"), - ))?, + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + Err(errors::ConnectorError::NotImplemented( + crate::utils::get_unimplemented_payment_method_error_message("Helcim"), + ))? + } } } } @@ -271,13 +275,17 @@ impl TryFrom<&HelcimRouterData<&PaymentsAuthorizeRouterData>> for HelcimPayments | PaymentMethodData::Reward | PaymentMethodData::RealTimePayment(_) | PaymentMethodData::Upi(_) + | PaymentMethodData::MobilePayment(_) | PaymentMethodData::Voucher(_) | PaymentMethodData::GiftCard(_) | PaymentMethodData::OpenBanking(_) | PaymentMethodData::CardToken(_) - | PaymentMethodData::NetworkToken(_) => Err(errors::ConnectorError::NotImplemented( - crate::utils::get_unimplemented_payment_method_error_message("Helcim"), - ))?, + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + Err(errors::ConnectorError::NotImplemented( + crate::utils::get_unimplemented_payment_method_error_message("Helcim"), + ))? + } } } } @@ -373,8 +381,8 @@ impl resource_id: ResponseId::ConnectorTransactionId( item.response.transaction_id.to_string(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: item.response.invoice_number.clone(), @@ -424,8 +432,8 @@ impl Ok(Self { response: Ok(PaymentsResponseData::TransactionResponse { resource_id, - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata, network_txn_id: None, connector_response_reference_id: item.response.invoice_number.clone(), @@ -473,8 +481,8 @@ impl resource_id: ResponseId::ConnectorTransactionId( item.response.transaction_id.to_string(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: item.response.invoice_number.clone(), @@ -553,8 +561,8 @@ impl resource_id: ResponseId::ConnectorTransactionId( item.response.transaction_id.to_string(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: item.response.invoice_number.clone(), @@ -610,8 +618,8 @@ impl resource_id: ResponseId::ConnectorTransactionId( item.response.transaction_id.to_string(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: item.response.invoice_number.clone(), diff --git a/crates/hyperswitch_connectors/src/connectors/inespay.rs b/crates/hyperswitch_connectors/src/connectors/inespay.rs new file mode 100644 index 000000000000..3d3ea346b78a --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/inespay.rs @@ -0,0 +1,568 @@ +pub mod transformers; + +use common_utils::{ + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, +}; +use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::{ + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, + RefundSyncRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks, +}; +use masking::{ExposeInterface, Mask}; +use transformers as inespay; + +use crate::{constants::headers, types::ResponseRouterData, utils}; + +#[derive(Clone)] +pub struct Inespay { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl Inespay { + pub fn new() -> &'static Self { + &Self { + amount_converter: &StringMinorUnitForConnector, + } + } +} + +impl api::Payment for Inespay {} +impl api::PaymentSession for Inespay {} +impl api::ConnectorAccessToken for Inespay {} +impl api::MandateSetup for Inespay {} +impl api::PaymentAuthorize for Inespay {} +impl api::PaymentSync for Inespay {} +impl api::PaymentCapture for Inespay {} +impl api::PaymentVoid for Inespay {} +impl api::Refund for Inespay {} +impl api::RefundExecute for Inespay {} +impl api::RefundSync for Inespay {} +impl api::PaymentToken for Inespay {} + +impl ConnectorIntegration + for Inespay +{ + // Not Implemented (R) +} + +impl ConnectorCommonExt for Inespay +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } +} + +impl ConnectorCommon for Inespay { + fn id(&self) -> &'static str { + "inespay" + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Base + // TODO! Check connector documentation, on which unit they are processing the currency. + // If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor, + // if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { + connectors.inespay.base_url.as_ref() + } + + fn get_auth_header( + &self, + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + let auth = inespay::InespayAuthType::try_from(auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + Ok(vec![( + headers::AUTHORIZATION.to_string(), + auth.api_key.expose().into_masked(), + )]) + } + + fn build_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: inespay::InespayErrorResponse = res + .response + .parse_struct("InespayErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.code, + message: response.message, + reason: response.reason, + attempt_status: None, + connector_transaction_id: None, + }) + } +} + +impl ConnectorValidation for Inespay { + //TODO: implement functions when support enabled +} + +impl ConnectorIntegration for Inespay { + //TODO: implement sessions flow +} + +impl ConnectorIntegration for Inespay {} + +impl ConnectorIntegration for Inespay {} + +impl ConnectorIntegration for Inespay { + fn get_headers( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + + let connector_router_data = inespay::InespayRouterData::from((amount, req)); + let connector_req = inespay::InespayPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: inespay::InespayPaymentsResponse = res + .response + .parse_struct("Inespay PaymentsAuthorizeResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Inespay { + fn get_headers( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: inespay::InespayPaymentsResponse = res + .response + .parse_struct("inespay PaymentsSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Inespay { + fn get_headers( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsCaptureType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCaptureType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCaptureRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: inespay::InespayPaymentsResponse = res + .response + .parse_struct("Inespay PaymentsCaptureResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Inespay {} + +impl ConnectorIntegration for Inespay { + fn get_headers( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let refund_amount = utils::convert_amount( + self.amount_converter, + req.request.minor_refund_amount, + req.request.currency, + )?; + + let connector_router_data = inespay::InespayRouterData::from((refund_amount, req)); + let connector_req = inespay::InespayRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundExecuteType::get_headers( + self, req, connectors, + )?) + .set_body(types::RefundExecuteType::get_request_body( + self, req, connectors, + )?) + .build(); + Ok(Some(request)) + } + + fn handle_response( + &self, + data: &RefundsRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult, errors::ConnectorError> { + let response: inespay::RefundResponse = res + .response + .parse_struct("inespay RefundResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Inespay { + fn get_headers( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &RefundSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::RefundSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundSyncType::get_headers(self, req, connectors)?) + .set_body(types::RefundSyncType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &RefundSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: inespay::RefundResponse = res + .response + .parse_struct("inespay RefundSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +#[async_trait::async_trait] +impl webhooks::IncomingWebhook for Inespay { + fn get_webhook_object_reference_id( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_event_type( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_resource_object( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } +} + +impl ConnectorSpecifications for Inespay {} diff --git a/crates/hyperswitch_connectors/src/connectors/inespay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/inespay/transformers.rs new file mode 100644 index 000000000000..296d76546c84 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/inespay/transformers.rs @@ -0,0 +1,228 @@ +use common_enums::enums; +use common_utils::types::StringMinorUnit; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{ConnectorAuthType, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{PaymentsAuthorizeRouterData, RefundsRouterData}, +}; +use hyperswitch_interfaces::errors; +use masking::Secret; +use serde::{Deserialize, Serialize}; + +use crate::{ + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::PaymentsAuthorizeRequestData, +}; + +//TODO: Fill the struct with respective fields +pub struct InespayRouterData { + pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub router_data: T, +} + +impl From<(StringMinorUnit, T)> for InespayRouterData { + fn from((amount, item): (StringMinorUnit, T)) -> Self { + //Todo : use utils to convert the amount to the type of amount that a connector accepts + Self { + amount, + router_data: item, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, PartialEq)] +pub struct InespayPaymentsRequest { + amount: StringMinorUnit, + card: InespayCard, +} + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct InespayCard { + number: cards::CardNumber, + expiry_month: Secret, + expiry_year: Secret, + cvc: Secret, + complete: bool, +} + +impl TryFrom<&InespayRouterData<&PaymentsAuthorizeRouterData>> for InespayPaymentsRequest { + type Error = error_stack::Report; + fn try_from( + item: &InespayRouterData<&PaymentsAuthorizeRouterData>, + ) -> Result { + match item.router_data.request.payment_method_data.clone() { + PaymentMethodData::Card(req_card) => { + let card = InespayCard { + number: req_card.card_number, + expiry_month: req_card.card_exp_month, + expiry_year: req_card.card_exp_year, + cvc: req_card.card_cvc, + complete: item.router_data.request.is_auto_capture()?, + }; + Ok(Self { + amount: item.amount.clone(), + card, + }) + } + _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), + } + } +} + +//TODO: Fill the struct with respective fields +// Auth Struct +pub struct InespayAuthType { + pub(super) api_key: Secret, +} + +impl TryFrom<&ConnectorAuthType> for InespayAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &ConnectorAuthType) -> Result { + match auth_type { + ConnectorAuthType::HeaderKey { api_key } => Ok(Self { + api_key: api_key.to_owned(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} +// PaymentsResponse +//TODO: Append the remaining status flags +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum InespayPaymentStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for common_enums::AttemptStatus { + fn from(item: InespayPaymentStatus) -> Self { + match item { + InespayPaymentStatus::Succeeded => Self::Charged, + InespayPaymentStatus::Failed => Self::Failure, + InespayPaymentStatus::Processing => Self::Authorizing, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct InespayPaymentsResponse { + status: InespayPaymentStatus, + id: String, +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + Ok(Self { + status: common_enums::AttemptStatus::from(item.response.status), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +// REFUND : +// Type definition for RefundRequest +#[derive(Default, Debug, Serialize)] +pub struct InespayRefundRequest { + pub amount: StringMinorUnit, +} + +impl TryFrom<&InespayRouterData<&RefundsRouterData>> for InespayRefundRequest { + type Error = error_stack::Report; + fn try_from(item: &InespayRouterData<&RefundsRouterData>) -> Result { + Ok(Self { + amount: item.amount.to_owned(), + }) + } +} + +// Type definition for Refund Response + +#[allow(dead_code)] +#[derive(Debug, Serialize, Default, Deserialize, Clone)] +pub enum RefundStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for enums::RefundStatus { + fn from(item: RefundStatus) -> Self { + match item { + RefundStatus::Succeeded => Self::Success, + RefundStatus::Failed => Self::Failure, + RefundStatus::Processing => Self::Pending, + //TODO: Review mapping + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct RefundResponse { + id: String, + status: RefundStatus, +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct InespayErrorResponse { + pub status_code: u16, + pub code: String, + pub message: String, + pub reason: Option, +} diff --git a/crates/hyperswitch_connectors/src/connectors/jpmorgan.rs b/crates/hyperswitch_connectors/src/connectors/jpmorgan.rs new file mode 100644 index 000000000000..2689e1a62d0f --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/jpmorgan.rs @@ -0,0 +1,828 @@ +pub mod transformers; +use base64::Engine; +use common_enums::enums; +use common_utils::{ + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, MinorUnit, MinorUnitForConnector}, +}; +use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::{ + router_data::{AccessToken, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + consts, errors, + events::connector_api_logs::ConnectorEvent, + types::{self, RefreshTokenType, Response}, + webhooks, +}; +use masking::{Mask, Maskable, PeekInterface}; +use transformers::{self as jpmorgan, JpmorganErrorResponse}; + +use crate::{ + constants::headers, + types::{RefreshTokenRouterData, ResponseRouterData}, + utils, +}; + +#[derive(Clone)] +pub struct Jpmorgan { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl Jpmorgan { + pub fn new() -> &'static Self { + &Self { + amount_converter: &MinorUnitForConnector, + } + } +} + +impl api::Payment for Jpmorgan {} +impl api::PaymentSession for Jpmorgan {} +impl api::ConnectorAccessToken for Jpmorgan {} +impl api::MandateSetup for Jpmorgan {} +impl api::PaymentAuthorize for Jpmorgan {} +impl api::PaymentSync for Jpmorgan {} +impl api::PaymentCapture for Jpmorgan {} +impl api::PaymentVoid for Jpmorgan {} +impl api::Refund for Jpmorgan {} +impl api::RefundExecute for Jpmorgan {} +impl api::RefundSync for Jpmorgan {} +impl api::PaymentToken for Jpmorgan {} + +impl ConnectorIntegration + for Jpmorgan +{ + // Not Implemented (R) +} + +impl ConnectorCommonExt for Jpmorgan +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut headers = vec![( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + )]; + let auth_header = ( + headers::AUTHORIZATION.to_string(), + format!( + "Bearer {}", + req.access_token + .clone() + .ok_or(errors::ConnectorError::FailedToObtainAuthType)? + .token + .peek() + ) + .into_masked(), + ); + let request_id = ( + headers::REQUEST_ID.to_string(), + req.connector_request_reference_id + .clone() + .to_string() + .into_masked(), + ); + let merchant_id = ( + headers::MERCHANT_ID.to_string(), + req.merchant_id.get_string_repr().to_string().into_masked(), + ); + headers.push(auth_header); + headers.push(request_id); + headers.push(merchant_id); + Ok(headers) + } +} + +impl ConnectorCommon for Jpmorgan { + fn id(&self) -> &'static str { + "jpmorgan" + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Minor + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { + connectors.jpmorgan.base_url.as_ref() + } + + fn build_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: JpmorganErrorResponse = res + .response + .parse_struct("JpmorganErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + router_env::logger::info!(connector_response=?response); + event_builder.map(|i| i.set_response_body(&response)); + + let response_message = response + .response_message + .as_ref() + .map_or_else(|| consts::NO_ERROR_MESSAGE.to_string(), ToString::to_string); + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.response_code, + message: response_message.clone(), + reason: Some(response_message), + attempt_status: None, + connector_transaction_id: None, + }) + } +} + +impl ConnectorValidation for Jpmorgan { + fn validate_connector_against_payment_request( + &self, + capture_method: Option, + _payment_method: enums::PaymentMethod, + _pmt: Option, + ) -> CustomResult<(), errors::ConnectorError> { + let capture_method = capture_method.unwrap_or_default(); + match capture_method { + enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::ManualMultiple + | enums::CaptureMethod::Scheduled + | enums::CaptureMethod::SequentialAutomatic => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Jpmorgan"), + ))? + } + } + } + + fn validate_psync_reference_id( + &self, + data: &PaymentsSyncData, + _is_three_ds: bool, + _status: enums::AttemptStatus, + _connector_meta_data: Option, + ) -> CustomResult<(), errors::ConnectorError> { + if data.encoded_data.is_some() + || data + .connector_transaction_id + .get_connector_transaction_id() + .is_ok() + { + return Ok(()); + } + Err(errors::ConnectorError::MissingConnectorTransactionID.into()) + } +} + +impl ConnectorIntegration for Jpmorgan { + //TODO: implement sessions flow +} + +impl ConnectorIntegration for Jpmorgan { + fn get_headers( + &self, + req: &RefreshTokenRouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let client_id = req.request.app_id.clone(); + + let client_secret = req.request.id.clone(); + + let creds = format!( + "{}:{}", + client_id.peek(), + client_secret.unwrap_or_default().peek() + ); + let encoded_creds = common_utils::consts::BASE64_ENGINE.encode(creds); + + let auth_string = format!("Basic {}", encoded_creds); + Ok(vec![ + ( + headers::CONTENT_TYPE.to_string(), + RefreshTokenType::get_content_type(self).to_string().into(), + ), + ( + headers::AUTHORIZATION.to_string(), + auth_string.into_masked(), + ), + ]) + } + + fn get_content_type(&self) -> &'static str { + "application/x-www-form-urlencoded" + } + + fn get_url( + &self, + _req: &RefreshTokenRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!( + "{}/am/oauth2/alpha/access_token", + connectors + .jpmorgan + .secondary_base_url + .as_ref() + .ok_or(errors::ConnectorError::FailedToObtainIntegrationUrl)? + )) + } + + fn get_request_body( + &self, + req: &RefreshTokenRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = jpmorgan::JpmorganAuthUpdateRequest::try_from(req)?; + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &RefreshTokenRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .attach_default_headers() + .headers(RefreshTokenType::get_headers(self, req, connectors)?) + .url(&RefreshTokenType::get_url(self, req, connectors)?) + .set_body(RefreshTokenType::get_request_body(self, req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &RefreshTokenRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: jpmorgan::JpmorganAuthUpdateResponse = res + .response + .parse_struct("jpmorgan JpmorganAuthUpdateResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration + for Jpmorgan +{ +} + +impl ConnectorIntegration for Jpmorgan { + fn get_headers( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!("{}/payments", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount: MinorUnit = utils::convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + + let connector_router_data = jpmorgan::JpmorganRouterData::from((amount, req)); + let connector_req = jpmorgan::JpmorganPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: jpmorgan::JpmorganPaymentsResponse = res + .response + .parse_struct("Jpmorgan PaymentsAuthorizeResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Jpmorgan { + fn get_headers( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult { + let tid = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}/payments/{}/captures", + self.base_url(connectors), + tid + )) + } + + fn get_request_body( + &self, + req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount: MinorUnit = utils::convert_amount( + self.amount_converter, + req.request.minor_amount_to_capture, + req.request.currency, + )?; + + let connector_router_data = jpmorgan::JpmorganRouterData::from((amount, req)); + let connector_req = jpmorgan::JpmorganCaptureRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsCaptureType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCaptureType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCaptureRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: jpmorgan::JpmorganPaymentsResponse = res + .response + .parse_struct("Jpmorgan PaymentsCaptureResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Jpmorgan { + fn get_headers( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult { + let tid = req + .request + .connector_transaction_id + .get_connector_transaction_id() + .change_context(errors::ConnectorError::MissingConnectorTransactionID)?; + Ok(format!("{}/payments/{}", self.base_url(connectors), tid)) + } + + fn build_request( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: jpmorgan::JpmorganPaymentsResponse = res + .response + .parse_struct("jpmorgan PaymentsSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Jpmorgan { + fn get_headers( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult { + let tid = req.request.connector_transaction_id.clone(); + Ok(format!("{}/payments/{}", self.base_url(connectors), tid)) + } + + fn get_request_body( + &self, + req: &PaymentsCancelRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount: MinorUnit = utils::convert_amount( + self.amount_converter, + req.request.minor_amount.unwrap_or_default(), + req.request.currency.unwrap_or_default(), + )?; + + let connector_router_data = jpmorgan::JpmorganRouterData::from((amount, req)); + let connector_req = jpmorgan::JpmorganCancelRequest::try_from(connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Patch) + .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) + .set_body(types::PaymentsVoidType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCancelRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: jpmorgan::JpmorganCancelResponse = res + .response + .parse_struct("JpmrorganPaymentsVoidResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Jpmorgan { + fn get_headers( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("Refunds".to_string()).into()) + } + + fn get_request_body( + &self, + req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let refund_amount = utils::convert_amount( + self.amount_converter, + req.request.minor_refund_amount, + req.request.currency, + )?; + + let connector_router_data = jpmorgan::JpmorganRouterData::from((refund_amount, req)); + let connector_req = jpmorgan::JpmorganRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundExecuteType::get_headers( + self, req, connectors, + )?) + .set_body(types::RefundExecuteType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &RefundsRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult, errors::ConnectorError> { + let response: jpmorgan::JpmorganRefundResponse = res + .response + .parse_struct("JpmorganRefundResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Jpmorgan { + fn get_headers( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + fn get_url( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult { + let tid = req.request.connector_transaction_id.clone(); + Ok(format!("{}/refunds/{}", self.base_url(connectors), tid)) + } + fn build_request( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::RefundSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundSyncType::get_headers(self, req, connectors)?) + .set_body(types::RefundSyncType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &RefundSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: jpmorgan::JpmorganRefundSyncResponse = res + .response + .parse_struct("jpmorgan RefundSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +#[async_trait::async_trait] +impl webhooks::IncomingWebhook for Jpmorgan { + fn get_webhook_object_reference_id( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_event_type( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_resource_object( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } +} + +impl ConnectorSpecifications for Jpmorgan {} diff --git a/crates/hyperswitch_connectors/src/connectors/jpmorgan/transformers.rs b/crates/hyperswitch_connectors/src/connectors/jpmorgan/transformers.rs new file mode 100644 index 000000000000..78c1b5eefb5f --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/jpmorgan/transformers.rs @@ -0,0 +1,742 @@ +use common_enums::enums::CaptureMethod; +use common_utils::types::MinorUnit; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{AccessToken, ConnectorAuthType, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::{PaymentsCancelData, ResponseId}, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + RefreshTokenRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::errors; +use masking::Secret; +use serde::{Deserialize, Serialize}; + +use crate::{ + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::{ + get_unimplemented_payment_method_error_message, CardData, RouterData as OtherRouterData, + }, +}; +pub struct JpmorganRouterData { + pub amount: MinorUnit, + pub router_data: T, +} + +impl From<(MinorUnit, T)> for JpmorganRouterData { + fn from((amount, item): (MinorUnit, T)) -> Self { + Self { + amount, + router_data: item, + } + } +} + +#[derive(Debug, Clone, Serialize)] +pub struct JpmorganAuthUpdateRequest { + pub grant_type: String, + pub scope: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct JpmorganAuthUpdateResponse { + pub access_token: Secret, + pub scope: String, + pub token_type: String, + pub expires_in: i64, +} + +impl TryFrom<&RefreshTokenRouterData> for JpmorganAuthUpdateRequest { + type Error = error_stack::Report; + fn try_from(_item: &RefreshTokenRouterData) -> Result { + Ok(Self { + grant_type: String::from("client_credentials"), + scope: String::from("jpm:payments:sandbox"), + }) + } +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(AccessToken { + token: item.response.access_token, + expires: item.response.expires_in, + }), + ..item.data + }) + } +} + +#[derive(Default, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct JpmorganPaymentsRequest { + capture_method: CapMethod, + amount: MinorUnit, + currency: common_enums::Currency, + merchant: JpmorganMerchant, + payment_method_type: JpmorganPaymentMethodType, +} + +#[derive(Default, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct JpmorganCard { + account_number: Secret, + expiry: Expiry, +} + +#[derive(Default, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct JpmorganPaymentMethodType { + card: JpmorganCard, +} + +#[derive(Default, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Expiry { + month: Secret, + year: Secret, +} + +#[derive(Serialize, Debug, Default, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct JpmorganMerchantSoftware { + company_name: Secret, + product_name: Secret, +} + +#[derive(Default, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct JpmorganMerchant { + merchant_software: JpmorganMerchantSoftware, +} + +fn map_capture_method( + capture_method: CaptureMethod, +) -> Result> { + match capture_method { + CaptureMethod::Automatic => Ok(CapMethod::Now), + CaptureMethod::Manual => Ok(CapMethod::Manual), + CaptureMethod::Scheduled + | CaptureMethod::ManualMultiple + | CaptureMethod::SequentialAutomatic => { + Err(errors::ConnectorError::NotImplemented("Capture Method".to_string()).into()) + } + } +} + +impl TryFrom<&JpmorganRouterData<&PaymentsAuthorizeRouterData>> for JpmorganPaymentsRequest { + type Error = error_stack::Report; + fn try_from( + item: &JpmorganRouterData<&PaymentsAuthorizeRouterData>, + ) -> Result { + match item.router_data.request.payment_method_data.clone() { + PaymentMethodData::Card(req_card) => { + if item.router_data.is_three_ds() { + return Err(errors::ConnectorError::NotSupported { + message: "3DS payments".to_string(), + connector: "Jpmorgan", + } + .into()); + } + + let capture_method = + map_capture_method(item.router_data.request.capture_method.unwrap_or_default()); + + let merchant_software = JpmorganMerchantSoftware { + company_name: String::from("JPMC").into(), + product_name: String::from("Hyperswitch").into(), + }; + + let merchant = JpmorganMerchant { merchant_software }; + + let expiry: Expiry = Expiry { + month: req_card.card_exp_month.clone(), + year: req_card.get_expiry_year_4_digit(), + }; + + let account_number = Secret::new(req_card.card_number.to_string()); + + let card = JpmorganCard { + account_number, + expiry, + }; + + let payment_method_type = JpmorganPaymentMethodType { card }; + + Ok(Self { + capture_method: capture_method?, + currency: item.router_data.request.currency, + amount: item.amount, + merchant, + payment_method_type, + }) + } + PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::CardRedirect(_) + | PaymentMethodData::Wallet(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) => Err(errors::ConnectorError::NotImplemented( + get_unimplemented_payment_method_error_message("jpmorgan"), + ) + .into()), + } + } +} + +//JP Morgan uses access token only due to which we aren't reading the fields in this struct +#[derive(Debug)] +pub struct JpmorganAuthType { + pub(super) _api_key: Secret, + pub(super) _key1: Secret, +} + +impl TryFrom<&ConnectorAuthType> for JpmorganAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &ConnectorAuthType) -> Result { + match auth_type { + ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { + _api_key: api_key.to_owned(), + _key1: key1.to_owned(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "UPPERCASE")] +pub enum JpmorganTransactionStatus { + Success, + Denied, + Error, +} + +#[derive(Default, Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "UPPERCASE")] +pub enum JpmorganTransactionState { + Closed, + Authorized, + Voided, + #[default] + Pending, + Declined, + Error, +} + +#[derive(Default, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct JpmorganPaymentsResponse { + transaction_id: String, + request_id: String, + transaction_state: JpmorganTransactionState, + response_status: String, + response_code: String, + response_message: String, + payment_method_type: PaymentMethodType, + capture_method: Option, +} + +#[derive(Default, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Merchant { + merchant_id: Option, + merchant_software: MerchantSoftware, + merchant_category_code: Option, +} + +#[derive(Default, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MerchantSoftware { + company_name: Secret, + product_name: Secret, + version: Option>, +} + +#[derive(Default, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PaymentMethodType { + card: Option, +} + +#[derive(Default, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Card { + expiry: Option, + card_type: Option>, + card_type_name: Option>, + masked_account_number: Option>, + card_type_indicators: Option, + network_response: Option, +} + +#[derive(Default, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NetworkResponse { + address_verification_result: Option>, + address_verification_result_code: Option>, + card_verification_result_code: Option>, +} + +#[derive(Default, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ExpiryResponse { + month: Option>, + year: Option>, +} + +#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CardTypeIndicators { + issuance_country_code: Option>, + is_durbin_regulated: Option, + card_product_types: Secret>, +} + +pub fn attempt_status_from_transaction_state( + transaction_state: JpmorganTransactionState, +) -> common_enums::AttemptStatus { + match transaction_state { + JpmorganTransactionState::Authorized => common_enums::AttemptStatus::Authorized, + JpmorganTransactionState::Closed => common_enums::AttemptStatus::Charged, + JpmorganTransactionState::Declined | JpmorganTransactionState::Error => { + common_enums::AttemptStatus::Failure + } + JpmorganTransactionState::Pending => common_enums::AttemptStatus::Pending, + JpmorganTransactionState::Voided => common_enums::AttemptStatus::Voided, + } +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + + fn try_from( + item: ResponseRouterData, + ) -> Result { + let transaction_state = match item.response.transaction_state { + JpmorganTransactionState::Closed => match item.response.capture_method { + Some(CapMethod::Now) => JpmorganTransactionState::Closed, + _ => JpmorganTransactionState::Authorized, + }, + JpmorganTransactionState::Authorized => JpmorganTransactionState::Authorized, + JpmorganTransactionState::Voided => JpmorganTransactionState::Voided, + JpmorganTransactionState::Pending => JpmorganTransactionState::Pending, + JpmorganTransactionState::Declined => JpmorganTransactionState::Declined, + JpmorganTransactionState::Error => JpmorganTransactionState::Error, + }; + let status = attempt_status_from_transaction_state(transaction_state); + + Ok(Self { + status, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + item.response.transaction_id.clone(), + ), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(item.response.transaction_id.clone()), + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } +} + +#[derive(Default, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct JpmorganCaptureRequest { + capture_method: Option, + amount: MinorUnit, + currency: Option, +} + +#[derive(Debug, Default, Copy, Serialize, Deserialize, Clone)] +#[serde(rename_all = "UPPERCASE")] +pub enum CapMethod { + #[default] + Now, + Delayed, + Manual, +} + +impl TryFrom<&JpmorganRouterData<&PaymentsCaptureRouterData>> for JpmorganCaptureRequest { + type Error = error_stack::Report; + fn try_from( + item: &JpmorganRouterData<&PaymentsCaptureRouterData>, + ) -> Result { + let capture_method = Some(map_capture_method( + item.router_data.request.capture_method.unwrap_or_default(), + )?); + Ok(Self { + capture_method, + amount: item.amount, + currency: Some(item.router_data.request.currency), + }) + } +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct JpmorganCaptureResponse { + pub transaction_id: String, + pub request_id: String, + pub transaction_state: JpmorganTransactionState, + pub response_status: JpmorganTransactionStatus, + pub response_code: String, + pub response_message: String, + pub payment_method_type: PaymentMethodTypeCapRes, +} + +#[derive(Default, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PaymentMethodTypeCapRes { + pub card: Option, +} + +#[derive(Default, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CardCapRes { + pub card_type: Option>, + pub card_type_name: Option>, + unmasked_account_number: Option>, +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + let status = attempt_status_from_transaction_state(item.response.transaction_state); + Ok(Self { + status, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + item.response.transaction_id.clone(), + ), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(item.response.transaction_id.clone()), + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct JpmorganPSyncResponse { + transaction_id: String, + transaction_state: JpmorganTransactionState, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "UPPERCASE")] +pub enum JpmorganResponseStatus { + Success, + Denied, + Error, +} + +impl + TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + let status = attempt_status_from_transaction_state(item.response.transaction_state); + Ok(Self { + status, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + item.response.transaction_id.clone(), + ), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(item.response.transaction_id.clone()), + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct TransactionData { + payment_type: Option>, + status_code: Secret, + txn_secret: Option>, + tid: Option>, + test_mode: Option>, + status: Option, +} + +#[derive(Default, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct JpmorganRefundRequest { + pub merchant: MerchantRefundReq, + pub amount: MinorUnit, +} + +#[derive(Default, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MerchantRefundReq { + pub merchant_software: MerchantSoftware, +} + +impl TryFrom<&JpmorganRouterData<&RefundsRouterData>> for JpmorganRefundRequest { + type Error = error_stack::Report; + fn try_from(_item: &JpmorganRouterData<&RefundsRouterData>) -> Result { + Err(errors::ConnectorError::NotImplemented("Refunds".to_string()).into()) + } +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct JpmorganRefundResponse { + pub transaction_id: Option, + pub request_id: String, + pub transaction_state: JpmorganTransactionState, + pub amount: MinorUnit, + pub currency: common_enums::Currency, + pub response_status: JpmorganResponseStatus, + pub response_code: String, + pub response_message: String, + pub transaction_reference_id: Option, + pub remaining_refundable_amount: Option, +} + +#[allow(dead_code)] +#[derive(Debug, Serialize, Default, Deserialize, Clone)] +pub enum RefundStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for common_enums::RefundStatus { + fn from(item: RefundStatus) -> Self { + match item { + RefundStatus::Succeeded => Self::Success, + RefundStatus::Failed => Self::Failure, + RefundStatus::Processing => Self::Pending, + } + } +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct RefundResponse { + id: String, + status: RefundStatus, +} + +pub fn refund_status_from_transaction_state( + transaction_state: JpmorganTransactionState, +) -> common_enums::RefundStatus { + match transaction_state { + JpmorganTransactionState::Voided | JpmorganTransactionState::Closed => { + common_enums::RefundStatus::Success + } + JpmorganTransactionState::Declined | JpmorganTransactionState::Error => { + common_enums::RefundStatus::Failure + } + JpmorganTransactionState::Pending | JpmorganTransactionState::Authorized => { + common_enums::RefundStatus::Pending + } + } +} + +impl TryFrom> + for RefundsRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item + .response + .transaction_id + .clone() + .ok_or(errors::ConnectorError::ResponseHandlingFailed)?, + refund_status: refund_status_from_transaction_state( + item.response.transaction_state, + ), + }), + ..item.data + }) + } +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct JpmorganRefundSyncResponse { + transaction_id: String, + request_id: String, + transaction_state: JpmorganTransactionState, + amount: MinorUnit, + currency: common_enums::Currency, + response_status: JpmorganResponseStatus, + response_code: String, +} + +impl TryFrom> + for RefundsRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.transaction_id.clone(), + refund_status: refund_status_from_transaction_state( + item.response.transaction_state, + ), + }), + ..item.data + }) + } +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct JpmorganCancelRequest { + pub amount: Option, + pub is_void: Option, + pub reversal_reason: Option, +} + +impl TryFrom> for JpmorganCancelRequest { + type Error = error_stack::Report; + fn try_from(item: JpmorganRouterData<&PaymentsCancelRouterData>) -> Result { + Ok(Self { + amount: item.router_data.request.amount, + is_void: Some(true), + reversal_reason: item.router_data.request.cancellation_reason.clone(), + }) + } +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct JpmorganCancelResponse { + transaction_id: String, + request_id: String, + response_status: JpmorganResponseStatus, + response_code: String, + response_message: String, + payment_method_type: JpmorganPaymentMethodTypeCancelResponse, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct JpmorganPaymentMethodTypeCancelResponse { + pub card: CardCancelResponse, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CardCancelResponse { + pub card_type: Secret, + pub card_type_name: Secret, +} + +impl + TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + F, + JpmorganCancelResponse, + PaymentsCancelData, + PaymentsResponseData, + >, + ) -> Result { + let status = match item.response.response_status { + JpmorganResponseStatus::Success => common_enums::AttemptStatus::Voided, + JpmorganResponseStatus::Denied | JpmorganResponseStatus::Error => { + common_enums::AttemptStatus::Failure + } + }; + Ok(Self { + status, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + item.response.transaction_id.clone(), + ), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(item.response.transaction_id.clone()), + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct JpmorganValidationErrors { + pub code: Option, + pub message: Option, + pub entity: Option, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct JpmorganErrorInformation { + pub code: Option, + pub message: Option, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct JpmorganErrorResponse { + pub response_status: JpmorganTransactionStatus, + pub response_code: String, + pub response_message: Option, +} diff --git a/crates/hyperswitch_connectors/src/connectors/mollie.rs b/crates/hyperswitch_connectors/src/connectors/mollie.rs index 5066fb422faf..8cdf4f03a703 100644 --- a/crates/hyperswitch_connectors/src/connectors/mollie.rs +++ b/crates/hyperswitch_connectors/src/connectors/mollie.rs @@ -1,12 +1,11 @@ pub mod transformers; -use std::fmt::Debug; - use common_enums::enums; use common_utils::{ errors::CustomResult, ext_traits::BytesExt, request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector}, }; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ @@ -31,7 +30,7 @@ use hyperswitch_domain_models::{ use hyperswitch_interfaces::{ api::{ self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorRedirectResponse, - ConnectorValidation, + ConnectorSpecifications, ConnectorValidation, }, configs::Connectors, consts, errors, @@ -43,10 +42,20 @@ use masking::{Mask, PeekInterface}; use transformers as mollie; // use self::mollie::{webhook_headers, VoltWebhookBodyEventType}; -use crate::{constants::headers, types::ResponseRouterData}; +use crate::{constants::headers, types::ResponseRouterData, utils::convert_amount}; -#[derive(Debug, Clone)] -pub struct Mollie; +#[derive(Clone)] +pub struct Mollie { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl Mollie { + pub fn new() -> &'static Self { + &Self { + amount_converter: &StringMajorUnitForConnector, + } + } +} impl api::Payment for Mollie {} impl api::PaymentSession for Mollie {} @@ -254,12 +263,13 @@ impl ConnectorIntegration CustomResult { - let router_obj = mollie::MollieRouterData::try_from(( - &self.get_currency_unit(), + let amount = convert_amount( + self.amount_converter, + req.request.minor_amount, req.request.currency, - req.request.amount, - req, - ))?; + )?; + + let router_obj = mollie::MollieRouterData::from((amount, req)); let connector_req = mollie::MolliePaymentsRequest::try_from(&router_obj)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -448,12 +458,13 @@ impl ConnectorIntegration for Mollie req: &RefundsRouterData, _connectors: &Connectors, ) -> CustomResult { - let router_obj = mollie::MollieRouterData::try_from(( - &self.get_currency_unit(), + let amount = convert_amount( + self.amount_converter, + req.request.minor_refund_amount, req.request.currency, - req.request.refund_amount, - req, - ))?; + )?; + + let router_obj = mollie::MollieRouterData::from((amount, req)); let connector_req = mollie::MollieRefundRequest::try_from(&router_obj)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -623,3 +634,5 @@ impl ConnectorRedirectResponse for Mollie { } } } + +impl ConnectorSpecifications for Mollie {} diff --git a/crates/hyperswitch_connectors/src/connectors/mollie/transformers.rs b/crates/hyperswitch_connectors/src/connectors/mollie/transformers.rs index d05259216321..5a641327c96e 100644 --- a/crates/hyperswitch_connectors/src/connectors/mollie/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/mollie/transformers.rs @@ -1,6 +1,6 @@ use cards::CardNumber; use common_enums::enums; -use common_utils::{pii::Email, request::Method}; +use common_utils::{pii::Email, request::Method, types::StringMajorUnit}; use hyperswitch_domain_models::{ payment_method_data::{BankDebitData, BankRedirectData, PaymentMethodData, WalletData}, router_data::{ConnectorAuthType, PaymentMethodToken, RouterData}, @@ -8,7 +8,7 @@ use hyperswitch_domain_models::{ router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData}, types, }; -use hyperswitch_interfaces::{api, errors}; +use hyperswitch_interfaces::errors; use masking::{ExposeInterface, Secret}; use serde::{Deserialize, Serialize}; use url::Url; @@ -17,7 +17,7 @@ use crate::{ types::{RefundsResponseRouterData, ResponseRouterData}, unimplemented_payment_method, utils::{ - self, AddressDetailsData, BrowserInformationData, CardData as CardDataUtil, + AddressDetailsData, BrowserInformationData, CardData as CardDataUtil, PaymentMethodTokenizationRequestData, PaymentsAuthorizeRequestData, RouterData as _, }, }; @@ -26,26 +26,16 @@ type Error = error_stack::Report; #[derive(Debug, Serialize)] pub struct MollieRouterData { - pub amount: String, + pub amount: StringMajorUnit, pub router_data: T, } -impl TryFrom<(&api::CurrencyUnit, enums::Currency, i64, T)> for MollieRouterData { - type Error = error_stack::Report; - - fn try_from( - (currency_unit, currency, amount, router_data): ( - &api::CurrencyUnit, - enums::Currency, - i64, - T, - ), - ) -> Result { - let amount = utils::get_amount_as_string(currency_unit, amount, currency)?; - Ok(Self { +impl From<(StringMajorUnit, T)> for MollieRouterData { + fn from((amount, router_data): (StringMajorUnit, T)) -> Self { + Self { amount, router_data, - }) + } } } @@ -68,7 +58,7 @@ pub struct MolliePaymentsRequest { #[derive(Default, Debug, Serialize, Deserialize)] pub struct Amount { currency: enums::Currency, - value: String, + value: StringMajorUnit, } #[derive(Debug, Serialize)] @@ -162,10 +152,10 @@ impl TryFrom<&MollieRouterData<&types::PaymentsAuthorizeRouterData>> for MollieP value: item.amount.clone(), }; let description = item.router_data.get_description()?; - let redirect_url = item.router_data.request.get_return_url()?; + let redirect_url = item.router_data.request.get_router_return_url()?; let payment_method_data = match item.router_data.request.capture_method.unwrap_or_default() { - enums::CaptureMethod::Automatic => { + enums::CaptureMethod::Automatic | enums::CaptureMethod::SequentialAutomatic => { match &item.router_data.request.payment_method_data { PaymentMethodData::Card(_) => { let pm_token = item.router_data.get_payment_method_token()?; @@ -182,6 +172,9 @@ impl TryFrom<&MollieRouterData<&types::PaymentsAuthorizeRouterData>> for MollieP "Mollie" ))? } + PaymentMethodToken::PazeDecrypt(_) => { + Err(unimplemented_payment_method!("Paze", "Mollie"))? + } }), }, ))) @@ -361,7 +354,7 @@ fn get_billing_details( } fn get_address_details( - address: Option<&api_models::payments::AddressDetails>, + address: Option<&hyperswitch_domain_models::address::AddressDetails>, ) -> Result, Error> { let address_details = match address { Some(address) => { @@ -514,8 +507,8 @@ impl TryFrom, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { Ok(vec![]) } } @@ -70,14 +87,14 @@ impl ConnectorCommon for Multisafepay { "application/json" } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.multisafepay.base_url.as_ref() } fn get_auth_header( &self, - auth_type: &types::ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { let auth = multisafepay::MultisafepayAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; Ok(vec![( @@ -101,26 +118,27 @@ impl ConnectorCommon for Multisafepay { let attempt_status = Option::::from(response.clone()); - Ok(ErrorResponse::foreign_from(( + Ok(multisafepay::populate_error_reason( Some(response.error_code.to_string()), Some(response.error_info.clone()), Some(response.error_info), res.status_code, attempt_status, None, - ))) + )) } } impl ConnectorValidation for Multisafepay { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic => Ok(()), + enums::CaptureMethod::Automatic | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::Manual | enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err(errors::ConnectorError::NotImplemented( @@ -133,7 +151,7 @@ impl ConnectorValidation for Multisafepay { fn validate_mandate_payment( &self, pm_type: Option, - pm_data: types::domain::payments::PaymentMethodData, + pm_data: PaymentMethodData, ) -> CustomResult<(), errors::ConnectorError> { let mandate_supported_pmd = std::collections::HashSet::from([ PaymentMethodDataType::Card, @@ -147,33 +165,21 @@ impl api::Payment for Multisafepay {} impl api::PaymentToken for Multisafepay {} -impl - ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Multisafepay +impl ConnectorIntegration + for Multisafepay { // Not Implemented (R) } impl api::MandateSetup for Multisafepay {} -impl - ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Multisafepay +impl ConnectorIntegration + for Multisafepay { fn build_request( &self, - _req: &types::RouterData< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - >, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err(errors::ConnectorError::NotImplemented( "Setup Mandate flow for Multisafepay".to_string(), ) @@ -183,27 +189,19 @@ impl impl api::PaymentVoid for Multisafepay {} -impl ConnectorIntegration - for Multisafepay -{ -} +impl ConnectorIntegration for Multisafepay {} impl api::ConnectorAccessToken for Multisafepay {} -impl ConnectorIntegration - for Multisafepay -{ -} +impl ConnectorIntegration for Multisafepay {} impl api::PaymentSync for Multisafepay {} -impl ConnectorIntegration - for Multisafepay -{ +impl ConnectorIntegration for Multisafepay { fn get_headers( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -213,8 +211,8 @@ impl ConnectorIntegration CustomResult { let url = self.base_url(connectors); let api_key = multisafepay::MultisafepayAuthType::try_from(&req.connector_auth_type) @@ -227,12 +225,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) + RequestBuilder::new() + .method(Method::Get) .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) @@ -250,10 +248,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: multisafepay::MultisafepayAuthResponse = res .response .parse_struct("multisafepay PaymentsResponse") @@ -262,7 +260,7 @@ impl ConnectorIntegration - for Multisafepay -{ -} +impl ConnectorIntegration for Multisafepay {} impl api::PaymentSession for Multisafepay {} -impl ConnectorIntegration - for Multisafepay -{ +impl ConnectorIntegration for Multisafepay { //TODO: implement sessions flow } impl api::PaymentAuthorize for Multisafepay {} -impl ConnectorIntegration - for Multisafepay -{ +impl ConnectorIntegration for Multisafepay { fn get_headers( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -304,8 +295,8 @@ impl ConnectorIntegration CustomResult { let url = self.base_url(connectors); let api_key = multisafepay::MultisafepayAuthType::try_from(&req.connector_auth_type) @@ -317,10 +308,10 @@ impl ConnectorIntegration CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_amount, req.request.currency, @@ -333,12 +324,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsAuthorizeType::get_url( self, req, connectors, )?) @@ -355,10 +346,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: multisafepay::MultisafepayAuthResponse = res .response .parse_struct("MultisafepayPaymentsResponse") @@ -367,7 +358,7 @@ impl ConnectorIntegration - for Multisafepay -{ +impl ConnectorIntegration for Multisafepay { fn get_headers( &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -405,8 +394,8 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, + req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { let url = self.base_url(connectors); let api_key = multisafepay::MultisafepayAuthType::try_from(&req.connector_auth_type) @@ -421,10 +410,10 @@ impl ConnectorIntegration, - _connectors: &settings::Connectors, + req: &RefundsRouterData, + _connectors: &Connectors, ) -> CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_refund_amount, req.request.currency, @@ -438,11 +427,11 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) .url(&types::RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundExecuteType::get_headers( @@ -457,17 +446,17 @@ impl ConnectorIntegration, + data: &RefundsRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult, errors::ConnectorError> { let response: multisafepay::MultisafepayRefundResponse = res .response .parse_struct("multisafepay RefundResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -484,14 +473,12 @@ impl ConnectorIntegration - for Multisafepay -{ +impl ConnectorIntegration for Multisafepay { fn get_headers( &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -501,8 +488,8 @@ impl ConnectorIntegration CustomResult { let url = self.base_url(connectors); let api_key = multisafepay::MultisafepayAuthType::try_from(&req.connector_auth_type) @@ -517,12 +504,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) + RequestBuilder::new() + .method(Method::Get) .url(&types::RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundSyncType::get_headers(self, req, connectors)?) @@ -532,17 +519,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: multisafepay::MultisafepayRefundResponse = res .response .parse_struct("multisafepay RefundResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -560,25 +547,27 @@ impl ConnectorIntegration, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_event_type( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { - Ok(api::IncomingWebhookEvent::EventNotSupported) + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Ok(IncomingWebhookEvent::EventNotSupported) } fn get_webhook_resource_object( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for Multisafepay {} diff --git a/crates/router/src/connector/multisafepay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/multisafepay/transformers.rs similarity index 68% rename from crates/router/src/connector/multisafepay/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/multisafepay/transformers.rs index 547a38ad8498..a60e8bdc79ed 100644 --- a/crates/router/src/connector/multisafepay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/multisafepay/transformers.rs @@ -1,21 +1,33 @@ -use api_models::enums::BankNames; -use common_enums::AttemptStatus; +use common_enums::{enums, AttemptStatus, BankNames}; use common_utils::{ + errors::ParsingError, pii::{Email, IpAddress}, + request::Method, types::{FloatMajorUnit, MinorUnit}, }; -use masking::ExposeInterface; +use hyperswitch_domain_models::{ + payment_method_data::{BankRedirectData, PayLaterData, PaymentMethodData, WalletData}, + router_data::{ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{ + MandateReference, PaymentsResponseData, RedirectForm, RefundsResponseData, + }, + types::{self}, +}; +use hyperswitch_interfaces::{ + consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE}, + errors, +}; +use masking::{ExposeInterface, Secret}; use serde::{Deserialize, Serialize}; use url::Url; use crate::{ - connector::utils::{ - self, AddressDetailsData, CardData, PaymentsAuthorizeRequestData, RouterData, + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::{ + self, AddressDetailsData, CardData as _, PaymentsAuthorizeRequestData, RouterData as _, }, - core::errors, - pii::Secret, - services, - types::{self, api, domain, storage::enums, transformers::ForeignFrom}, }; #[derive(Debug, Serialize)] @@ -472,143 +484,145 @@ impl TryFrom<&MultisafepayRouterData<&types::PaymentsAuthorizeRouterData>> item: &MultisafepayRouterData<&types::PaymentsAuthorizeRouterData>, ) -> Result { let payment_type = match item.router_data.request.payment_method_data { - domain::PaymentMethodData::Card(ref _ccard) => Type::Direct, - domain::PaymentMethodData::MandatePayment => Type::Direct, - domain::PaymentMethodData::Wallet(ref wallet_data) => match wallet_data { - domain::WalletData::GooglePay(_) => Type::Direct, - domain::WalletData::PaypalRedirect(_) => Type::Redirect, - domain::WalletData::AliPayQr(_) - | domain::WalletData::AliPayRedirect(_) - | domain::WalletData::AliPayHkRedirect(_) - | domain::WalletData::MomoRedirect(_) - | domain::WalletData::KakaoPayRedirect(_) - | domain::WalletData::GoPayRedirect(_) - | domain::WalletData::GcashRedirect(_) - | domain::WalletData::ApplePay(_) - | domain::WalletData::ApplePayRedirect(_) - | domain::WalletData::ApplePayThirdPartySdk(_) - | domain::WalletData::DanaRedirect {} - | domain::WalletData::GooglePayRedirect(_) - | domain::WalletData::GooglePayThirdPartySdk(_) - | domain::WalletData::MbWayRedirect(_) - | domain::WalletData::MobilePayRedirect(_) - | domain::WalletData::PaypalSdk(_) - | domain::WalletData::SamsungPay(_) - | domain::WalletData::TwintRedirect {} - | domain::WalletData::VippsRedirect {} - | domain::WalletData::TouchNGoRedirect(_) - | domain::WalletData::WeChatPayRedirect(_) - | domain::WalletData::WeChatPayQr(_) - | domain::WalletData::CashappQr(_) - | domain::WalletData::SwishQr(_) - | domain::WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( + PaymentMethodData::Card(ref _ccard) => Type::Direct, + PaymentMethodData::MandatePayment => Type::Direct, + PaymentMethodData::Wallet(ref wallet_data) => match wallet_data { + WalletData::GooglePay(_) => Type::Direct, + WalletData::PaypalRedirect(_) => Type::Redirect, + WalletData::AliPayQr(_) + | WalletData::AliPayRedirect(_) + | WalletData::AliPayHkRedirect(_) + | WalletData::MomoRedirect(_) + | WalletData::KakaoPayRedirect(_) + | WalletData::GoPayRedirect(_) + | WalletData::GcashRedirect(_) + | WalletData::ApplePay(_) + | WalletData::ApplePayRedirect(_) + | WalletData::ApplePayThirdPartySdk(_) + | WalletData::DanaRedirect {} + | WalletData::GooglePayRedirect(_) + | WalletData::GooglePayThirdPartySdk(_) + | WalletData::MbWayRedirect(_) + | WalletData::MobilePayRedirect(_) + | WalletData::PaypalSdk(_) + | WalletData::Paze(_) + | WalletData::SamsungPay(_) + | WalletData::TwintRedirect {} + | WalletData::VippsRedirect {} + | WalletData::TouchNGoRedirect(_) + | WalletData::WeChatPayRedirect(_) + | WalletData::WeChatPayQr(_) + | WalletData::CashappQr(_) + | WalletData::SwishQr(_) + | WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("multisafepay"), ))?, }, - domain::PaymentMethodData::BankRedirect(ref bank_data) => match bank_data { - domain::BankRedirectData::Giropay { .. } => Type::Redirect, - domain::BankRedirectData::Ideal { .. } => Type::Direct, - domain::BankRedirectData::BancontactCard { .. } - | domain::BankRedirectData::Bizum { .. } - | domain::BankRedirectData::Blik { .. } - | domain::BankRedirectData::Eps { .. } - | domain::BankRedirectData::Interac { .. } - | domain::BankRedirectData::OnlineBankingCzechRepublic { .. } - | domain::BankRedirectData::OnlineBankingFinland { .. } - | domain::BankRedirectData::OnlineBankingPoland { .. } - | domain::BankRedirectData::OnlineBankingSlovakia { .. } - | domain::BankRedirectData::OpenBankingUk { .. } - | domain::BankRedirectData::Przelewy24 { .. } - | domain::BankRedirectData::Sofort { .. } - | domain::BankRedirectData::Trustly { .. } - | domain::BankRedirectData::OnlineBankingFpx { .. } - | domain::BankRedirectData::OnlineBankingThailand { .. } - | domain::BankRedirectData::LocalBankRedirect {} => { + PaymentMethodData::BankRedirect(ref bank_data) => match bank_data { + BankRedirectData::Giropay { .. } => Type::Redirect, + BankRedirectData::Ideal { .. } => Type::Direct, + BankRedirectData::BancontactCard { .. } + | BankRedirectData::Bizum { .. } + | BankRedirectData::Blik { .. } + | BankRedirectData::Eps { .. } + | BankRedirectData::Interac { .. } + | BankRedirectData::OnlineBankingCzechRepublic { .. } + | BankRedirectData::OnlineBankingFinland { .. } + | BankRedirectData::OnlineBankingPoland { .. } + | BankRedirectData::OnlineBankingSlovakia { .. } + | BankRedirectData::OpenBankingUk { .. } + | BankRedirectData::Przelewy24 { .. } + | BankRedirectData::Sofort { .. } + | BankRedirectData::Trustly { .. } + | BankRedirectData::OnlineBankingFpx { .. } + | BankRedirectData::OnlineBankingThailand { .. } + | BankRedirectData::LocalBankRedirect {} => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("multisafepay"), ))? } }, - domain::PaymentMethodData::PayLater(ref _paylater) => Type::Redirect, + PaymentMethodData::PayLater(ref _paylater) => Type::Redirect, _ => Type::Redirect, }; let gateway = match item.router_data.request.payment_method_data { - domain::PaymentMethodData::Card(ref ccard) => { + PaymentMethodData::Card(ref ccard) => { Some(Gateway::try_from(ccard.get_card_issuer()?)?) } - domain::PaymentMethodData::Wallet(ref wallet_data) => Some(match wallet_data { - domain::WalletData::GooglePay(_) => Gateway::Googlepay, - domain::WalletData::PaypalRedirect(_) => Gateway::Paypal, - domain::WalletData::AliPayQr(_) - | domain::WalletData::AliPayRedirect(_) - | domain::WalletData::AliPayHkRedirect(_) - | domain::WalletData::MomoRedirect(_) - | domain::WalletData::KakaoPayRedirect(_) - | domain::WalletData::GoPayRedirect(_) - | domain::WalletData::GcashRedirect(_) - | domain::WalletData::ApplePay(_) - | domain::WalletData::ApplePayRedirect(_) - | domain::WalletData::ApplePayThirdPartySdk(_) - | domain::WalletData::DanaRedirect {} - | domain::WalletData::GooglePayRedirect(_) - | domain::WalletData::GooglePayThirdPartySdk(_) - | domain::WalletData::MbWayRedirect(_) - | domain::WalletData::MobilePayRedirect(_) - | domain::WalletData::PaypalSdk(_) - | domain::WalletData::SamsungPay(_) - | domain::WalletData::TwintRedirect {} - | domain::WalletData::VippsRedirect {} - | domain::WalletData::TouchNGoRedirect(_) - | domain::WalletData::WeChatPayRedirect(_) - | domain::WalletData::WeChatPayQr(_) - | domain::WalletData::CashappQr(_) - | domain::WalletData::SwishQr(_) - | domain::WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( + PaymentMethodData::Wallet(ref wallet_data) => Some(match wallet_data { + WalletData::GooglePay(_) => Gateway::Googlepay, + WalletData::PaypalRedirect(_) => Gateway::Paypal, + WalletData::AliPayQr(_) + | WalletData::AliPayRedirect(_) + | WalletData::AliPayHkRedirect(_) + | WalletData::MomoRedirect(_) + | WalletData::KakaoPayRedirect(_) + | WalletData::GoPayRedirect(_) + | WalletData::GcashRedirect(_) + | WalletData::ApplePay(_) + | WalletData::ApplePayRedirect(_) + | WalletData::ApplePayThirdPartySdk(_) + | WalletData::DanaRedirect {} + | WalletData::GooglePayRedirect(_) + | WalletData::GooglePayThirdPartySdk(_) + | WalletData::MbWayRedirect(_) + | WalletData::MobilePayRedirect(_) + | WalletData::PaypalSdk(_) + | WalletData::Paze(_) + | WalletData::SamsungPay(_) + | WalletData::TwintRedirect {} + | WalletData::VippsRedirect {} + | WalletData::TouchNGoRedirect(_) + | WalletData::WeChatPayRedirect(_) + | WalletData::WeChatPayQr(_) + | WalletData::CashappQr(_) + | WalletData::SwishQr(_) + | WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("multisafepay"), ))?, }), - domain::PaymentMethodData::BankRedirect(ref bank_data) => Some(match bank_data { - domain::BankRedirectData::Giropay { .. } => Gateway::Giropay, - domain::BankRedirectData::Ideal { .. } => Gateway::Ideal, - domain::BankRedirectData::BancontactCard { .. } - | domain::BankRedirectData::Bizum { .. } - | domain::BankRedirectData::Blik { .. } - | domain::BankRedirectData::Eps { .. } - | domain::BankRedirectData::Interac { .. } - | domain::BankRedirectData::OnlineBankingCzechRepublic { .. } - | domain::BankRedirectData::OnlineBankingFinland { .. } - | domain::BankRedirectData::OnlineBankingPoland { .. } - | domain::BankRedirectData::OnlineBankingSlovakia { .. } - | domain::BankRedirectData::OpenBankingUk { .. } - | domain::BankRedirectData::Przelewy24 { .. } - | domain::BankRedirectData::Sofort { .. } - | domain::BankRedirectData::Trustly { .. } - | domain::BankRedirectData::OnlineBankingFpx { .. } - | domain::BankRedirectData::OnlineBankingThailand { .. } - | domain::BankRedirectData::LocalBankRedirect {} => { + PaymentMethodData::BankRedirect(ref bank_data) => Some(match bank_data { + BankRedirectData::Giropay { .. } => Gateway::Giropay, + BankRedirectData::Ideal { .. } => Gateway::Ideal, + BankRedirectData::BancontactCard { .. } + | BankRedirectData::Bizum { .. } + | BankRedirectData::Blik { .. } + | BankRedirectData::Eps { .. } + | BankRedirectData::Interac { .. } + | BankRedirectData::OnlineBankingCzechRepublic { .. } + | BankRedirectData::OnlineBankingFinland { .. } + | BankRedirectData::OnlineBankingPoland { .. } + | BankRedirectData::OnlineBankingSlovakia { .. } + | BankRedirectData::OpenBankingUk { .. } + | BankRedirectData::Przelewy24 { .. } + | BankRedirectData::Sofort { .. } + | BankRedirectData::Trustly { .. } + | BankRedirectData::OnlineBankingFpx { .. } + | BankRedirectData::OnlineBankingThailand { .. } + | BankRedirectData::LocalBankRedirect {} => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("multisafepay"), ))? } }), - domain::PaymentMethodData::PayLater(domain::PayLaterData::KlarnaRedirect {}) => { - Some(Gateway::Klarna) - } - domain::PaymentMethodData::MandatePayment => None, - domain::PaymentMethodData::CardRedirect(_) - | domain::PaymentMethodData::PayLater(_) - | domain::PaymentMethodData::BankDebit(_) - | domain::PaymentMethodData::BankTransfer(_) - | domain::PaymentMethodData::Crypto(_) - | domain::PaymentMethodData::Reward - | domain::PaymentMethodData::RealTimePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + PaymentMethodData::PayLater(PayLaterData::KlarnaRedirect {}) => Some(Gateway::Klarna), + PaymentMethodData::MandatePayment => None, + PaymentMethodData::CardRedirect(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("multisafepay"), ))? @@ -670,7 +684,7 @@ impl TryFrom<&MultisafepayRouterData<&types::PaymentsAuthorizeRouterData>> }; let gateway_info = match item.router_data.request.payment_method_data { - domain::PaymentMethodData::Card(ref ccard) => Some(GatewayInfo::Card(CardInfo { + PaymentMethodData::Card(ref ccard) => Some(GatewayInfo::Card(CardInfo { card_number: Some(ccard.card_number.clone()), card_expiry_date: Some(Secret::new( (format!( @@ -687,8 +701,8 @@ impl TryFrom<&MultisafepayRouterData<&types::PaymentsAuthorizeRouterData>> moto: None, term_url: None, })), - domain::PaymentMethodData::Wallet(ref wallet_data) => match wallet_data { - domain::WalletData::GooglePay(ref google_pay) => { + PaymentMethodData::Wallet(ref wallet_data) => match wallet_data { + WalletData::GooglePay(ref google_pay) => { Some(GatewayInfo::Wallet(WalletInfo::GooglePay({ GpayInfo { payment_token: Some(Secret::new( @@ -697,48 +711,48 @@ impl TryFrom<&MultisafepayRouterData<&types::PaymentsAuthorizeRouterData>> } }))) } - domain::WalletData::PaypalRedirect(_) => None, - domain::WalletData::AliPayQr(_) - | domain::WalletData::AliPayRedirect(_) - | domain::WalletData::AliPayHkRedirect(_) - | domain::WalletData::MomoRedirect(_) - | domain::WalletData::KakaoPayRedirect(_) - | domain::WalletData::GoPayRedirect(_) - | domain::WalletData::GcashRedirect(_) - | domain::WalletData::ApplePay(_) - | domain::WalletData::ApplePayRedirect(_) - | domain::WalletData::ApplePayThirdPartySdk(_) - | domain::WalletData::DanaRedirect {} - | domain::WalletData::GooglePayRedirect(_) - | domain::WalletData::GooglePayThirdPartySdk(_) - | domain::WalletData::MbWayRedirect(_) - | domain::WalletData::MobilePayRedirect(_) - | domain::WalletData::PaypalSdk(_) - | domain::WalletData::SamsungPay(_) - | domain::WalletData::TwintRedirect {} - | domain::WalletData::VippsRedirect {} - | domain::WalletData::TouchNGoRedirect(_) - | domain::WalletData::WeChatPayRedirect(_) - | domain::WalletData::WeChatPayQr(_) - | domain::WalletData::CashappQr(_) - | domain::WalletData::SwishQr(_) - | domain::WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( + WalletData::PaypalRedirect(_) => None, + WalletData::AliPayQr(_) + | WalletData::AliPayRedirect(_) + | WalletData::AliPayHkRedirect(_) + | WalletData::MomoRedirect(_) + | WalletData::KakaoPayRedirect(_) + | WalletData::GoPayRedirect(_) + | WalletData::GcashRedirect(_) + | WalletData::ApplePay(_) + | WalletData::ApplePayRedirect(_) + | WalletData::ApplePayThirdPartySdk(_) + | WalletData::DanaRedirect {} + | WalletData::GooglePayRedirect(_) + | WalletData::GooglePayThirdPartySdk(_) + | WalletData::MbWayRedirect(_) + | WalletData::MobilePayRedirect(_) + | WalletData::PaypalSdk(_) + | WalletData::Paze(_) + | WalletData::SamsungPay(_) + | WalletData::TwintRedirect {} + | WalletData::VippsRedirect {} + | WalletData::TouchNGoRedirect(_) + | WalletData::WeChatPayRedirect(_) + | WalletData::WeChatPayQr(_) + | WalletData::CashappQr(_) + | WalletData::SwishQr(_) + | WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("multisafepay"), ))?, }, - domain::PaymentMethodData::PayLater(ref paylater) => { + PaymentMethodData::PayLater(ref paylater) => { Some(GatewayInfo::PayLater(PayLaterInfo { email: Some(match paylater { - domain::PayLaterData::KlarnaRedirect {} => { - item.router_data.get_billing_email()? - } - domain::PayLaterData::KlarnaSdk { token: _ } - | domain::PayLaterData::AffirmRedirect {} - | domain::PayLaterData::AfterpayClearpayRedirect {} - | domain::PayLaterData::PayBrightRedirect {} - | domain::PayLaterData::WalleyRedirect {} - | domain::PayLaterData::AlmaRedirect {} - | domain::PayLaterData::AtomeRedirect {} => { + PayLaterData::KlarnaRedirect {} => item.router_data.get_billing_email()?, + PayLaterData::KlarnaSdk { token: _ } + | PayLaterData::KlarnaCheckout {} + | PayLaterData::AffirmRedirect {} + | PayLaterData::AfterpayClearpayRedirect {} + | PayLaterData::PayBrightRedirect {} + | PayLaterData::WalleyRedirect {} + | PayLaterData::AlmaRedirect {} + | PayLaterData::AtomeRedirect {} => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message( "multisafepay", @@ -748,49 +762,49 @@ impl TryFrom<&MultisafepayRouterData<&types::PaymentsAuthorizeRouterData>> }), })) } - domain::PaymentMethodData::BankRedirect(ref bank_redirect_data) => { - match bank_redirect_data { - domain::BankRedirectData::Ideal { bank_name, .. } => Some( - GatewayInfo::BankRedirect(BankRedirectInfo::Ideal(IdealInfo { - issuer_id: MultisafepayBankNames::try_from(&bank_name.ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "ideal.bank_name", - }, - )?)?, - })), - ), - domain::BankRedirectData::BancontactCard { .. } - | domain::BankRedirectData::Bizum { .. } - | domain::BankRedirectData::Blik { .. } - | domain::BankRedirectData::Eps { .. } - | domain::BankRedirectData::Giropay { .. } - | domain::BankRedirectData::Interac { .. } - | domain::BankRedirectData::OnlineBankingCzechRepublic { .. } - | domain::BankRedirectData::OnlineBankingFinland { .. } - | domain::BankRedirectData::OnlineBankingPoland { .. } - | domain::BankRedirectData::OnlineBankingSlovakia { .. } - | domain::BankRedirectData::OpenBankingUk { .. } - | domain::BankRedirectData::Przelewy24 { .. } - | domain::BankRedirectData::Sofort { .. } - | domain::BankRedirectData::Trustly { .. } - | domain::BankRedirectData::OnlineBankingFpx { .. } - | domain::BankRedirectData::OnlineBankingThailand { .. } - | domain::BankRedirectData::LocalBankRedirect {} => None, - } - } - domain::PaymentMethodData::MandatePayment => None, - domain::PaymentMethodData::CardRedirect(_) - | domain::PaymentMethodData::BankDebit(_) - | domain::PaymentMethodData::BankTransfer(_) - | domain::PaymentMethodData::Crypto(_) - | domain::PaymentMethodData::Reward - | domain::PaymentMethodData::RealTimePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::NetworkToken(_) => { + PaymentMethodData::BankRedirect(ref bank_redirect_data) => match bank_redirect_data { + BankRedirectData::Ideal { bank_name, .. } => Some(GatewayInfo::BankRedirect( + BankRedirectInfo::Ideal(IdealInfo { + issuer_id: MultisafepayBankNames::try_from(&bank_name.ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "ideal.bank_name", + }, + )?)?, + }), + )), + BankRedirectData::BancontactCard { .. } + | BankRedirectData::Bizum { .. } + | BankRedirectData::Blik { .. } + | BankRedirectData::Eps { .. } + | BankRedirectData::Giropay { .. } + | BankRedirectData::Interac { .. } + | BankRedirectData::OnlineBankingCzechRepublic { .. } + | BankRedirectData::OnlineBankingFinland { .. } + | BankRedirectData::OnlineBankingPoland { .. } + | BankRedirectData::OnlineBankingSlovakia { .. } + | BankRedirectData::OpenBankingUk { .. } + | BankRedirectData::Przelewy24 { .. } + | BankRedirectData::Sofort { .. } + | BankRedirectData::Trustly { .. } + | BankRedirectData::OnlineBankingFpx { .. } + | BankRedirectData::OnlineBankingThailand { .. } + | BankRedirectData::LocalBankRedirect {} => None, + }, + PaymentMethodData::MandatePayment => None, + PaymentMethodData::CardRedirect(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("multisafepay"), ))? @@ -825,7 +839,9 @@ impl TryFrom<&MultisafepayRouterData<&types::PaymentsAuthorizeRouterData>> .and_then(|mandate_ids| match mandate_ids.mandate_reference_id { Some(api_models::payments::MandateReferenceId::ConnectorMandateId( connector_mandate_ids, - )) => connector_mandate_ids.connector_mandate_id.map(Secret::new), + )) => connector_mandate_ids + .get_connector_mandate_id() + .map(Secret::new), _ => None, }), days_active: Some(30), @@ -842,10 +858,10 @@ pub struct MultisafepayAuthType { pub(super) api_key: Secret, } -impl TryFrom<&types::ConnectorAuthType> for MultisafepayAuthType { +impl TryFrom<&ConnectorAuthType> for MultisafepayAuthType { type Error = error_stack::Report; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { - if let types::ConnectorAuthType::HeaderKey { api_key } = auth_type { + fn try_from(auth_type: &ConnectorAuthType) -> Result { + if let ConnectorAuthType::HeaderKey { api_key } = auth_type { Ok(Self { api_key: api_key.to_owned(), }) @@ -901,7 +917,6 @@ pub struct Data { } #[derive(Default, Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] - pub struct MultisafepayPaymentDetails { pub account_holder_name: Option>, pub account_id: Option>, @@ -925,21 +940,15 @@ pub struct MultisafepayPaymentsResponse { #[serde(untagged)] pub enum MultisafepayAuthResponse { ErrorResponse(MultisafepayErrorResponse), - PaymentResponse(MultisafepayPaymentsResponse), + PaymentResponse(Box), } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { - type Error = error_stack::Report; + type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< - F, - MultisafepayAuthResponse, - T, - types::PaymentsResponseData, - >, + item: ResponseRouterData, ) -> Result { match item.response { MultisafepayAuthResponse::PaymentResponse(payment_response) => { @@ -947,7 +956,7 @@ impl .data .payment_url .clone() - .map(|url| services::RedirectForm::from((url, services::Method::Get))); + .map(|url| RedirectForm::from((url, Method::Get))); let default_status = if payment_response.success { MultisafepayPaymentStatus::Initialized @@ -961,29 +970,32 @@ impl Ok(Self { status, response: if utils::is_payment_failure(status) { - Err(types::ErrorResponse::foreign_from(( + Err(populate_error_reason( payment_response.data.reason_code, payment_response.data.reason.clone(), payment_response.data.reason, item.http_code, Some(status), Some(payment_response.data.order_id), - ))) + )) } else { - Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( payment_response.data.order_id.clone(), ), - redirection_data, - mandate_reference: payment_response - .data - .payment_details - .and_then(|payment_details| payment_details.recurring_id) - .map(|id| types::MandateReference { - connector_mandate_id: Some(id.expose()), - payment_method_id: None, - mandate_metadata: None, - }), + redirection_data: Box::new(redirection_data), + mandate_reference: Box::new( + payment_response + .data + .payment_details + .and_then(|payment_details| payment_details.recurring_id) + .map(|id| MandateReference { + connector_mandate_id: Some(id.expose()), + payment_method_id: None, + mandate_metadata: None, + connector_mandate_request_reference_id: None, + }), + ), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some( @@ -999,26 +1011,42 @@ impl MultisafepayAuthResponse::ErrorResponse(error_response) => { let attempt_status = Option::::from(error_response.clone()); Ok(Self { - response: Err(types::ErrorResponse::foreign_from(( + response: Err(populate_error_reason( Some(error_response.error_code.to_string()), Some(error_response.error_info.clone()), Some(error_response.error_info), item.http_code, attempt_status, None, - ))), + )), ..item.data }) } } } } - +pub fn populate_error_reason( + code: Option, + message: Option, + reason: Option, + http_code: u16, + attempt_status: Option, + connector_transaction_id: Option, +) -> ErrorResponse { + ErrorResponse { + code: code.unwrap_or(NO_ERROR_CODE.to_string()), + message: message.clone().unwrap_or(NO_ERROR_MESSAGE.to_string()), + reason, + status_code: http_code, + attempt_status, + connector_transaction_id, + } +} // REFUND : // Type definition for RefundRequest #[derive(Debug, Serialize)] pub struct MultisafepayRefundRequest { - pub currency: diesel_models::enums::Currency, + pub currency: enums::Currency, pub amount: MinorUnit, pub description: Option, pub refund_order_id: Option, @@ -1084,12 +1112,12 @@ pub enum MultisafepayRefundResponse { RefundResponse(RefundResponse), } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for types::RefundsRouterData { - type Error = error_stack::Report; + type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { match item.response { MultisafepayRefundResponse::RefundResponse(refund_data) => { @@ -1100,7 +1128,7 @@ impl TryFrom { let attempt_status = Option::::from(error_response.clone()); Ok(Self { - response: Err(types::ErrorResponse { + response: Err(ErrorResponse { code: error_response.error_code.to_string(), message: error_response.error_info.clone(), reason: Some(error_response.error_info), @@ -1125,12 +1153,12 @@ impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for types::RefundsRouterData { - type Error = error_stack::Report; + type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { match item.response { MultisafepayRefundResponse::RefundResponse(refund_data) => { @@ -1141,7 +1169,7 @@ impl TryFrom Ok(Self { - response: Err(types::ErrorResponse::foreign_from(( + response: Err(populate_error_reason( Some(error_response.error_code.to_string()), Some(error_response.error_info.clone()), Some(error_response.error_info), item.http_code, None, None, - ))), + )), ..item.data }), } diff --git a/crates/router/src/connector/nexinets.rs b/crates/hyperswitch_connectors/src/connectors/nexinets.rs similarity index 63% rename from crates/router/src/connector/nexinets.rs rename to crates/hyperswitch_connectors/src/connectors/nexinets.rs index 113b2924fec3..f93eef657245 100644 --- a/crates/router/src/connector/nexinets.rs +++ b/crates/hyperswitch_connectors/src/connectors/nexinets.rs @@ -1,32 +1,56 @@ pub mod transformers; -use std::fmt::Debug; - -use common_utils::request::RequestContent; +use api_models::webhooks::{IncomingWebhookEvent, ObjectReferenceId}; +use common_enums::enums; +use common_utils::{ + errors::CustomResult, + ext_traits::ByteSliceExt, + request::{Method, Request, RequestBuilder, RequestContent}, +}; use error_stack::{report, ResultExt}; -use transformers as nexinets; - -use crate::{ - configs::settings, - connector::{ - utils as connector_utils, - utils::{to_connector_meta, PaymentMethodDataType, PaymentsSyncRequestData}, +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, }, - core::errors::{self, CustomResult}, - events::connector_api_logs::ConnectorEvent, - headers, - services::{ - self, - request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, types::{ - self, - api::{self, ConnectorCommon, ConnectorCommonExt}, - storage::enums, - ErrorResponse, Response, + PaymentsAuthorizeType, PaymentsCaptureType, PaymentsSyncType, PaymentsVoidType, + RefundExecuteType, RefundSyncType, Response, + }, + webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, +}; +use masking::Mask; +use transformers as nexinets; + +use crate::{ + constants::headers, + types::ResponseRouterData, + utils::{ + construct_not_implemented_error_report, is_mandate_supported, to_connector_meta, + PaymentMethodDataType, PaymentsSyncRequestData, }, - utils::BytesExt, }; #[derive(Debug, Clone)] @@ -47,9 +71,9 @@ impl api::RefundSync for Nexinets {} impl Nexinets { pub fn connector_transaction_id( &self, - connector_meta: &Option, + connector_meta: Option<&serde_json::Value>, ) -> CustomResult, errors::ConnectorError> { - let meta: nexinets::NexinetsPaymentsMetadata = to_connector_meta(connector_meta.clone())?; + let meta: nexinets::NexinetsPaymentsMetadata = to_connector_meta(connector_meta.cloned())?; Ok(meta.transaction_id) } } @@ -60,9 +84,9 @@ where { fn build_headers( &self, - req: &types::RouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let mut header = vec![( headers::CONTENT_TYPE.to_string(), self.get_content_type().to_string().into(), @@ -82,14 +106,14 @@ impl ConnectorCommon for Nexinets { "application/json" } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.nexinets.base_url.as_ref() } fn get_auth_header( &self, - auth_type: &types::ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { let auth = nexinets::NexinetsAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; Ok(vec![( @@ -143,16 +167,19 @@ impl ConnectorCommon for Nexinets { } impl ConnectorValidation for Nexinets { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( - connector_utils::construct_not_implemented_error_report(capture_method, self.id()), + construct_not_implemented_error_report(capture_method, self.id()), ), } } @@ -160,7 +187,7 @@ impl ConnectorValidation for Nexinets { fn validate_mandate_payment( &self, pm_type: Option, - pm_data: types::domain::payments::PaymentMethodData, + pm_data: PaymentMethodData, ) -> CustomResult<(), errors::ConnectorError> { let mandate_supported_pmd = std::collections::HashSet::from([ PaymentMethodDataType::Card, @@ -170,36 +197,22 @@ impl ConnectorValidation for Nexinets { PaymentMethodDataType::Giropay, PaymentMethodDataType::Ideal, ]); - connector_utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) + is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) } } -impl ConnectorIntegration - for Nexinets -{ -} +impl ConnectorIntegration for Nexinets {} -impl ConnectorIntegration - for Nexinets -{ -} +impl ConnectorIntegration for Nexinets {} -impl - ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Nexinets +impl ConnectorIntegration + for Nexinets { fn build_request( &self, - _req: &types::RouterData< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - >, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err( errors::ConnectorError::NotImplemented("Setup Mandate flow for Nexinets".to_string()) .into(), @@ -207,14 +220,12 @@ impl } } -impl ConnectorIntegration - for Nexinets -{ +impl ConnectorIntegration for Nexinets { fn get_headers( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -224,10 +235,13 @@ impl ConnectorIntegration CustomResult { - let url = if req.request.capture_method == Some(enums::CaptureMethod::Automatic) { + let url = if matches!( + req.request.capture_method, + Some(enums::CaptureMethod::Automatic) | Some(enums::CaptureMethod::SequentialAutomatic) + ) { format!("{}/orders/debit", self.base_url(connectors)) } else { format!("{}/orders/preauth", self.base_url(connectors)) @@ -237,8 +251,8 @@ impl ConnectorIntegration CustomResult { let connector_req = nexinets::NexinetsPaymentsRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -246,20 +260,16 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsAuthorizeType::get_url( - self, req, connectors, - )?) + RequestBuilder::new() + .method(Method::Post) + .url(&PaymentsAuthorizeType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::PaymentsAuthorizeType::get_headers( - self, req, connectors, - )?) - .set_body(types::PaymentsAuthorizeType::get_request_body( + .headers(PaymentsAuthorizeType::get_headers(self, req, connectors)?) + .set_body(PaymentsAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -268,10 +278,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: nexinets::NexinetsPreAuthOrDebitResponse = res .response .parse_struct("Nexinets PaymentsAuthorizeResponse") @@ -280,7 +290,7 @@ impl ConnectorIntegration - for Nexinets -{ +impl ConnectorIntegration for Nexinets { fn get_headers( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -313,8 +321,8 @@ impl ConnectorIntegration CustomResult { let meta: nexinets::NexinetsPaymentsMetadata = to_connector_meta(req.request.connector_meta.clone())?; @@ -334,25 +342,25 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) - .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Get) + .url(&PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .headers(PaymentsSyncType::get_headers(self, req, connectors)?) .build(), )) } fn handle_response( &self, - data: &types::PaymentsSyncRouterData, + data: &PaymentsSyncRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: nexinets::NexinetsPaymentResponse = res .response .parse_struct("nexinets NexinetsPaymentResponse") @@ -361,7 +369,7 @@ impl ConnectorIntegration - for Nexinets -{ +impl ConnectorIntegration for Nexinets { fn get_headers( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -394,8 +400,8 @@ impl ConnectorIntegration CustomResult { let meta: nexinets::NexinetsPaymentsMetadata = to_connector_meta(req.request.connector_meta.clone())?; @@ -409,8 +415,8 @@ impl ConnectorIntegration CustomResult { let connector_req = nexinets::NexinetsCaptureOrVoidRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -418,18 +424,16 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Post) + .url(&PaymentsCaptureType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::PaymentsCaptureType::get_headers( - self, req, connectors, - )?) - .set_body(types::PaymentsCaptureType::get_request_body( + .headers(PaymentsCaptureType::get_headers(self, req, connectors)?) + .set_body(PaymentsCaptureType::get_request_body( self, req, connectors, )?) .build(), @@ -438,10 +442,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: nexinets::NexinetsPaymentResponse = res .response .parse_struct("NexinetsPaymentResponse") @@ -450,7 +454,7 @@ impl ConnectorIntegration - for Nexinets -{ +impl ConnectorIntegration for Nexinets { fn get_headers( &self, - req: &types::PaymentsCancelRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -483,8 +485,8 @@ impl ConnectorIntegration CustomResult { let meta: nexinets::NexinetsPaymentsMetadata = to_connector_meta(req.request.connector_meta.clone())?; @@ -498,8 +500,8 @@ impl ConnectorIntegration CustomResult { let connector_req = nexinets::NexinetsCaptureOrVoidRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -507,16 +509,14 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) - .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) - .set_body(types::PaymentsVoidType::get_request_body( - self, req, connectors, - )?) + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&PaymentsVoidType::get_url(self, req, connectors)?) + .headers(PaymentsVoidType::get_headers(self, req, connectors)?) + .set_body(PaymentsVoidType::get_request_body(self, req, connectors)?) .build(); Ok(Some(request)) @@ -524,10 +524,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: nexinets::NexinetsPaymentResponse = res .response .parse_struct("NexinetsPaymentResponse") @@ -536,7 +536,7 @@ impl ConnectorIntegration - for Nexinets -{ +impl ConnectorIntegration for Nexinets { fn get_headers( &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -569,8 +567,8 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, + req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { let meta: nexinets::NexinetsPaymentsMetadata = to_connector_meta(req.request.connector_metadata.clone())?; @@ -584,8 +582,8 @@ impl ConnectorIntegration, - _connectors: &settings::Connectors, + req: &RefundsRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_req = nexinets::NexinetsRefundRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -593,29 +591,25 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::RefundExecuteType::get_headers( - self, req, connectors, - )?) - .set_body(types::RefundExecuteType::get_request_body( - self, req, connectors, - )?) + .headers(RefundExecuteType::get_headers(self, req, connectors)?) + .set_body(RefundExecuteType::get_request_body(self, req, connectors)?) .build(); Ok(Some(request)) } fn handle_response( &self, - data: &types::RefundsRouterData, + data: &RefundsRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult, errors::ConnectorError> { let response: nexinets::NexinetsRefundResponse = res .response .parse_struct("nexinets RefundResponse") @@ -624,7 +618,7 @@ impl ConnectorIntegration for Nexinets { +impl ConnectorIntegration for Nexinets { fn get_headers( &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -655,8 +649,8 @@ impl ConnectorIntegration CustomResult { let transaction_id = req .request @@ -674,25 +668,25 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) - .url(&types::RefundSyncType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Get) + .url(&RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::RefundSyncType::get_headers(self, req, connectors)?) + .headers(RefundSyncType::get_headers(self, req, connectors)?) .build(), )) } fn handle_response( &self, - data: &types::RefundSyncRouterData, + data: &RefundSyncRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: nexinets::NexinetsRefundResponse = res .response .parse_struct("nexinets RefundSyncResponse") @@ -701,7 +695,7 @@ impl ConnectorIntegration, - ) -> CustomResult { + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_event_type( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { - Ok(api::IncomingWebhookEvent::EventNotSupported) + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Ok(IncomingWebhookEvent::EventNotSupported) } fn get_webhook_resource_object( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } @@ -743,12 +737,10 @@ impl api::IncomingWebhook for Nexinets { impl api::PaymentToken for Nexinets {} -impl - ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Nexinets +impl ConnectorIntegration + for Nexinets { // Not Implemented (R) } + +impl ConnectorSpecifications for Nexinets {} diff --git a/crates/router/src/connector/nexinets/transformers.rs b/crates/hyperswitch_connectors/src/connectors/nexinets/transformers.rs similarity index 66% rename from crates/router/src/connector/nexinets/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/nexinets/transformers.rs index 55b2fbe41763..8d6dc42926ba 100644 --- a/crates/router/src/connector/nexinets/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/nexinets/transformers.rs @@ -1,20 +1,33 @@ use base64::Engine; use cards::CardNumber; -use common_utils::errors::CustomResult; -use domain::PaymentMethodData; +use common_enums::{enums, AttemptStatus}; +use common_utils::{consts, errors::CustomResult, request::Method}; use error_stack::ResultExt; +use hyperswitch_domain_models::{ + payment_method_data::{ + ApplePayWalletData, BankRedirectData, Card, PaymentMethodData, WalletData, + }, + router_data::{ConnectorAuthType, RouterData}, + router_flow_types::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{ + MandateReference, PaymentsResponseData, RedirectForm, RefundsResponseData, + }, + types::{ + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + RefundsRouterData, + }, +}; +use hyperswitch_interfaces::errors; use masking::{ExposeInterface, PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use url::Url; use crate::{ - connector::utils::{ - self, CardData, PaymentsAuthorizeRequestData, PaymentsCancelRequestData, WalletData, + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::{ + self, CardData, PaymentsAuthorizeRequestData, PaymentsCancelRequestData, WalletData as _, }, - consts, - core::errors, - services, - types::{self, api, domain, storage::enums, transformers::ForeignFrom}, }; #[derive(Debug, Serialize)] @@ -109,7 +122,6 @@ pub struct NexinetsBankRedirects { #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] - pub struct NexinetsAsyncDetails { pub success_url: Option, pub cancel_url: Option, @@ -163,9 +175,9 @@ pub struct ApplepayPaymentMethod { token_type: String, } -impl TryFrom<&types::PaymentsAuthorizeRouterData> for NexinetsPaymentsRequest { +impl TryFrom<&PaymentsAuthorizeRouterData> for NexinetsPaymentsRequest { type Error = error_stack::Report; - fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result { + fn try_from(item: &PaymentsAuthorizeRouterData) -> Result { let return_url = item.request.router_return_url.clone(); let nexinets_async = NexinetsAsyncDetails { success_url: return_url.clone(), @@ -195,11 +207,11 @@ pub struct NexinetsAuthType { pub(super) api_key: Secret, } -impl TryFrom<&types::ConnectorAuthType> for NexinetsAuthType { +impl TryFrom<&ConnectorAuthType> for NexinetsAuthType { type Error = error_stack::Report; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { + fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - types::ConnectorAuthType::BodyKey { api_key, key1 } => { + ConnectorAuthType::BodyKey { api_key, key1 } => { let auth_key = format!("{}:{}", key1.peek(), api_key.peek()); let auth_header = format!("Basic {}", consts::BASE64_ENGINE.encode(auth_key)); Ok(Self { @@ -224,48 +236,48 @@ pub enum NexinetsPaymentStatus { Aborted, } -impl ForeignFrom<(NexinetsPaymentStatus, NexinetsTransactionType)> for enums::AttemptStatus { - fn foreign_from((status, method): (NexinetsPaymentStatus, NexinetsTransactionType)) -> Self { - match status { - NexinetsPaymentStatus::Success => match method { - NexinetsTransactionType::Preauth => Self::Authorized, - NexinetsTransactionType::Debit | NexinetsTransactionType::Capture => Self::Charged, - NexinetsTransactionType::Cancel => Self::Voided, - }, - NexinetsPaymentStatus::Declined - | NexinetsPaymentStatus::Failure - | NexinetsPaymentStatus::Expired - | NexinetsPaymentStatus::Aborted => match method { - NexinetsTransactionType::Preauth => Self::AuthorizationFailed, - NexinetsTransactionType::Debit | NexinetsTransactionType::Capture => { - Self::CaptureFailed - } - NexinetsTransactionType::Cancel => Self::VoidFailed, - }, - NexinetsPaymentStatus::Ok => match method { - NexinetsTransactionType::Preauth => Self::Authorized, - _ => Self::Pending, - }, - NexinetsPaymentStatus::Pending => Self::AuthenticationPending, - NexinetsPaymentStatus::InProgress => Self::Pending, - } +fn get_status(status: NexinetsPaymentStatus, method: NexinetsTransactionType) -> AttemptStatus { + match status { + NexinetsPaymentStatus::Success => match method { + NexinetsTransactionType::Preauth => AttemptStatus::Authorized, + NexinetsTransactionType::Debit | NexinetsTransactionType::Capture => { + AttemptStatus::Charged + } + NexinetsTransactionType::Cancel => AttemptStatus::Voided, + }, + NexinetsPaymentStatus::Declined + | NexinetsPaymentStatus::Failure + | NexinetsPaymentStatus::Expired + | NexinetsPaymentStatus::Aborted => match method { + NexinetsTransactionType::Preauth => AttemptStatus::AuthorizationFailed, + NexinetsTransactionType::Debit | NexinetsTransactionType::Capture => { + AttemptStatus::CaptureFailed + } + NexinetsTransactionType::Cancel => AttemptStatus::VoidFailed, + }, + NexinetsPaymentStatus::Ok => match method { + NexinetsTransactionType::Preauth => AttemptStatus::Authorized, + _ => AttemptStatus::Pending, + }, + NexinetsPaymentStatus::Pending => AttemptStatus::AuthenticationPending, + NexinetsPaymentStatus::InProgress => AttemptStatus::Pending, } } -impl TryFrom<&common_enums::enums::BankNames> for NexinetsBIC { +impl TryFrom<&enums::BankNames> for NexinetsBIC { type Error = error_stack::Report; - fn try_from(bank: &common_enums::enums::BankNames) -> Result { + fn try_from(bank: &enums::BankNames) -> Result { match bank { - common_enums::enums::BankNames::AbnAmro => Ok(Self::AbnAmro), - common_enums::enums::BankNames::AsnBank => Ok(Self::AsnBank), - common_enums::enums::BankNames::Bunq => Ok(Self::Bunq), - common_enums::enums::BankNames::Ing => Ok(Self::Ing), - common_enums::enums::BankNames::Knab => Ok(Self::Knab), - common_enums::enums::BankNames::Rabobank => Ok(Self::Rabobank), - common_enums::enums::BankNames::Regiobank => Ok(Self::Regiobank), - common_enums::enums::BankNames::SnsBank => Ok(Self::SnsBank), - common_enums::enums::BankNames::TriodosBank => Ok(Self::TriodosBank), - common_enums::enums::BankNames::VanLanschot => Ok(Self::VanLanschot), + enums::BankNames::AbnAmro => Ok(Self::AbnAmro), + enums::BankNames::AsnBank => Ok(Self::AsnBank), + enums::BankNames::Bunq => Ok(Self::Bunq), + enums::BankNames::Ing => Ok(Self::Ing), + enums::BankNames::Knab => Ok(Self::Knab), + enums::BankNames::Rabobank => Ok(Self::Rabobank), + enums::BankNames::Regiobank => Ok(Self::Regiobank), + enums::BankNames::SnsBank => Ok(Self::SnsBank), + enums::BankNames::TriodosBank => Ok(Self::TriodosBank), + enums::BankNames::VanLanschot => Ok(Self::VanLanschot), _ => Err(errors::ConnectorError::FlowNotSupported { flow: bank.to_string(), connector: "Nexinets".to_string(), @@ -311,24 +323,12 @@ pub struct NexinetsPaymentsMetadata { pub psync_flow: NexinetsTransactionType, } -impl - TryFrom< - types::ResponseRouterData< - F, - NexinetsPreAuthOrDebitResponse, - T, - types::PaymentsResponseData, - >, - > for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< - F, - NexinetsPreAuthOrDebitResponse, - T, - types::PaymentsResponseData, - >, + item: ResponseRouterData, ) -> Result { let transaction = match item.response.transactions.first() { Some(order) => order, @@ -343,11 +343,11 @@ impl let redirection_data = item .response .redirect_url - .map(|url| services::RedirectForm::from((url, services::Method::Get))); + .map(|url| RedirectForm::from((url, Method::Get))); let resource_id = match item.response.transaction_type.clone() { - NexinetsTransactionType::Preauth => types::ResponseId::NoResponseId, + NexinetsTransactionType::Preauth => ResponseId::NoResponseId, NexinetsTransactionType::Debit => { - types::ResponseId::ConnectorTransactionId(transaction.transaction_id.clone()) + ResponseId::ConnectorTransactionId(transaction.transaction_id.clone()) } _ => Err(errors::ConnectorError::ResponseHandlingFailed)?, }; @@ -355,20 +355,18 @@ impl .response .payment_instrument .payment_instrument_id - .map(|id| types::MandateReference { + .map(|id| MandateReference { connector_mandate_id: Some(id.expose()), payment_method_id: None, mandate_metadata: None, + connector_mandate_request_reference_id: None, }); Ok(Self { - status: enums::AttemptStatus::foreign_from(( - transaction.status.clone(), - item.response.transaction_type, - )), - response: Ok(types::PaymentsResponseData::TransactionResponse { + status: get_status(transaction.status.clone(), item.response.transaction_type), + response: Ok(PaymentsResponseData::TransactionResponse { resource_id, - redirection_data, - mandate_reference, + redirection_data: Box::new(redirection_data), + mandate_reference: Box::new(mandate_reference), connector_metadata: Some(connector_metadata), network_txn_id: None, connector_response_reference_id: Some(item.response.order_id), @@ -393,9 +391,9 @@ pub struct NexinetsOrder { pub order_id: String, } -impl TryFrom<&types::PaymentsCaptureRouterData> for NexinetsCaptureOrVoidRequest { +impl TryFrom<&PaymentsCaptureRouterData> for NexinetsCaptureOrVoidRequest { type Error = error_stack::Report; - fn try_from(item: &types::PaymentsCaptureRouterData) -> Result { + fn try_from(item: &PaymentsCaptureRouterData) -> Result { Ok(Self { initial_amount: item.request.amount_to_capture, currency: item.request.currency, @@ -403,9 +401,9 @@ impl TryFrom<&types::PaymentsCaptureRouterData> for NexinetsCaptureOrVoidRequest } } -impl TryFrom<&types::PaymentsCancelRouterData> for NexinetsCaptureOrVoidRequest { +impl TryFrom<&PaymentsCancelRouterData> for NexinetsCaptureOrVoidRequest { type Error = error_stack::Report; - fn try_from(item: &types::PaymentsCancelRouterData) -> Result { + fn try_from(item: &PaymentsCancelRouterData) -> Result { Ok(Self { initial_amount: item.request.get_amount()?, currency: item.request.get_currency()?, @@ -423,13 +421,12 @@ pub struct NexinetsPaymentResponse { pub transaction_type: NexinetsTransactionType, } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { let transaction_id = Some(item.response.transaction_id.clone()); let connector_metadata = serde_json::to_value(NexinetsPaymentsMetadata { @@ -440,19 +437,16 @@ impl .change_context(errors::ConnectorError::ResponseHandlingFailed)?; let resource_id = match item.response.transaction_type.clone() { NexinetsTransactionType::Debit | NexinetsTransactionType::Capture => { - types::ResponseId::ConnectorTransactionId(item.response.transaction_id) + ResponseId::ConnectorTransactionId(item.response.transaction_id) } - _ => types::ResponseId::NoResponseId, + _ => ResponseId::NoResponseId, }; Ok(Self { - status: enums::AttemptStatus::foreign_from(( - item.response.status, - item.response.transaction_type, - )), - response: Ok(types::PaymentsResponseData::TransactionResponse { + status: get_status(item.response.status, item.response.transaction_type), + response: Ok(PaymentsResponseData::TransactionResponse { resource_id, - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: Some(connector_metadata), network_txn_id: None, connector_response_reference_id: Some(item.response.order.order_id), @@ -473,9 +467,9 @@ pub struct NexinetsRefundRequest { pub currency: enums::Currency, } -impl TryFrom<&types::RefundsRouterData> for NexinetsRefundRequest { +impl TryFrom<&RefundsRouterData> for NexinetsRefundRequest { type Error = error_stack::Report; - fn try_from(item: &types::RefundsRouterData) -> Result { + fn try_from(item: &RefundsRouterData) -> Result { Ok(Self { initial_amount: item.request.refund_amount, currency: item.request.currency, @@ -521,15 +515,15 @@ impl From for enums::RefundStatus { } } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item.response.transaction_id, refund_status: enums::RefundStatus::from(item.response.status), }), @@ -538,15 +532,15 @@ impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item.response.transaction_id, refund_status: enums::RefundStatus::from(item.response.status), }), @@ -571,7 +565,7 @@ pub struct OrderErrorDetails { } fn get_payment_details_and_product( - item: &types::PaymentsAuthorizeRouterData, + item: &PaymentsAuthorizeRouterData, ) -> Result< (Option, NexinetsProduct), error_stack::Report, @@ -583,9 +577,9 @@ fn get_payment_details_and_product( )), PaymentMethodData::Wallet(wallet) => Ok(get_wallet_details(wallet)?), PaymentMethodData::BankRedirect(bank_redirect) => match bank_redirect { - domain::BankRedirectData::Eps { .. } => Ok((None, NexinetsProduct::Eps)), - domain::BankRedirectData::Giropay { .. } => Ok((None, NexinetsProduct::Giropay)), - domain::BankRedirectData::Ideal { bank_name, .. } => Ok(( + BankRedirectData::Eps { .. } => Ok((None, NexinetsProduct::Eps)), + BankRedirectData::Giropay { .. } => Ok((None, NexinetsProduct::Giropay)), + BankRedirectData::Ideal { bank_name, .. } => Ok(( Some(NexinetsPaymentDetails::BankRedirects(Box::new( NexinetsBankRedirects { bic: bank_name @@ -595,21 +589,21 @@ fn get_payment_details_and_product( ))), NexinetsProduct::Ideal, )), - domain::BankRedirectData::Sofort { .. } => Ok((None, NexinetsProduct::Sofort)), - domain::BankRedirectData::BancontactCard { .. } - | domain::BankRedirectData::Blik { .. } - | domain::BankRedirectData::Bizum { .. } - | domain::BankRedirectData::Interac { .. } - | domain::BankRedirectData::OnlineBankingCzechRepublic { .. } - | domain::BankRedirectData::OnlineBankingFinland { .. } - | domain::BankRedirectData::OnlineBankingPoland { .. } - | domain::BankRedirectData::OnlineBankingSlovakia { .. } - | domain::BankRedirectData::OpenBankingUk { .. } - | domain::BankRedirectData::Przelewy24 { .. } - | domain::BankRedirectData::Trustly { .. } - | domain::BankRedirectData::OnlineBankingFpx { .. } - | domain::BankRedirectData::OnlineBankingThailand { .. } - | domain::BankRedirectData::LocalBankRedirect {} => { + BankRedirectData::Sofort { .. } => Ok((None, NexinetsProduct::Sofort)), + BankRedirectData::BancontactCard { .. } + | BankRedirectData::Blik { .. } + | BankRedirectData::Bizum { .. } + | BankRedirectData::Interac { .. } + | BankRedirectData::OnlineBankingCzechRepublic { .. } + | BankRedirectData::OnlineBankingFinland { .. } + | BankRedirectData::OnlineBankingPoland { .. } + | BankRedirectData::OnlineBankingSlovakia { .. } + | BankRedirectData::OpenBankingUk { .. } + | BankRedirectData::Przelewy24 { .. } + | BankRedirectData::Trustly { .. } + | BankRedirectData::OnlineBankingFpx { .. } + | BankRedirectData::OnlineBankingThailand { .. } + | BankRedirectData::LocalBankRedirect {} => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("nexinets"), ))? @@ -623,20 +617,24 @@ fn get_payment_details_and_product( | PaymentMethodData::MandatePayment | PaymentMethodData::Reward | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) | PaymentMethodData::Upi(_) | PaymentMethodData::Voucher(_) | PaymentMethodData::GiftCard(_) | PaymentMethodData::OpenBanking(_) | PaymentMethodData::CardToken(_) - | PaymentMethodData::NetworkToken(_) => Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("nexinets"), - ))?, + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("nexinets"), + ))? + } } } fn get_card_data( - item: &types::PaymentsAuthorizeRouterData, - card: &domain::payments::Card, + item: &PaymentsAuthorizeRouterData, + card: &Card, ) -> Result { let (card_data, cof_contract) = match item.request.is_mandate_payment() { true => { @@ -663,10 +661,10 @@ fn get_card_data( } fn get_applepay_details( - wallet_data: &domain::WalletData, - applepay_data: &domain::ApplePayWalletData, + wallet_data: &WalletData, + applepay_data: &ApplePayWalletData, ) -> CustomResult { - let payment_data = wallet_data.get_wallet_token_as_json("Apple Pay".to_string())?; + let payment_data = WalletData::get_wallet_token_as_json(wallet_data, "Apple Pay".to_string())?; Ok(ApplePayDetails { payment_data, payment_method: ApplepayPaymentMethod { @@ -678,9 +676,7 @@ fn get_applepay_details( }) } -fn get_card_details( - req_card: &domain::payments::Card, -) -> Result { +fn get_card_details(req_card: &Card) -> Result { Ok(CardDetails { card_number: req_card.card_number.clone(), expiry_month: req_card.card_exp_month.clone(), @@ -690,14 +686,14 @@ fn get_card_details( } fn get_wallet_details( - wallet: &domain::WalletData, + wallet: &WalletData, ) -> Result< (Option, NexinetsProduct), error_stack::Report, > { match wallet { - domain::WalletData::PaypalRedirect(_) => Ok((None, NexinetsProduct::Paypal)), - domain::WalletData::ApplePay(applepay_data) => Ok(( + WalletData::PaypalRedirect(_) => Ok((None, NexinetsProduct::Paypal)), + WalletData::ApplePay(applepay_data) => Ok(( Some(NexinetsPaymentDetails::Wallet(Box::new( NexinetsWalletDetails::ApplePayToken(Box::new(get_applepay_details( wallet, @@ -706,31 +702,32 @@ fn get_wallet_details( ))), NexinetsProduct::Applepay, )), - domain::WalletData::AliPayQr(_) - | domain::WalletData::AliPayRedirect(_) - | domain::WalletData::AliPayHkRedirect(_) - | domain::WalletData::MomoRedirect(_) - | domain::WalletData::KakaoPayRedirect(_) - | domain::WalletData::GoPayRedirect(_) - | domain::WalletData::GcashRedirect(_) - | domain::WalletData::ApplePayRedirect(_) - | domain::WalletData::ApplePayThirdPartySdk(_) - | domain::WalletData::DanaRedirect { .. } - | domain::WalletData::GooglePay(_) - | domain::WalletData::GooglePayRedirect(_) - | domain::WalletData::GooglePayThirdPartySdk(_) - | domain::WalletData::MbWayRedirect(_) - | domain::WalletData::MobilePayRedirect(_) - | domain::WalletData::PaypalSdk(_) - | domain::WalletData::SamsungPay(_) - | domain::WalletData::TwintRedirect { .. } - | domain::WalletData::VippsRedirect { .. } - | domain::WalletData::TouchNGoRedirect(_) - | domain::WalletData::WeChatPayRedirect(_) - | domain::WalletData::WeChatPayQr(_) - | domain::WalletData::CashappQr(_) - | domain::WalletData::SwishQr(_) - | domain::WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( + WalletData::AliPayQr(_) + | WalletData::AliPayRedirect(_) + | WalletData::AliPayHkRedirect(_) + | WalletData::MomoRedirect(_) + | WalletData::KakaoPayRedirect(_) + | WalletData::GoPayRedirect(_) + | WalletData::GcashRedirect(_) + | WalletData::ApplePayRedirect(_) + | WalletData::ApplePayThirdPartySdk(_) + | WalletData::DanaRedirect { .. } + | WalletData::GooglePay(_) + | WalletData::GooglePayRedirect(_) + | WalletData::GooglePayThirdPartySdk(_) + | WalletData::MbWayRedirect(_) + | WalletData::MobilePayRedirect(_) + | WalletData::PaypalSdk(_) + | WalletData::Paze(_) + | WalletData::SamsungPay(_) + | WalletData::TwintRedirect { .. } + | WalletData::VippsRedirect { .. } + | WalletData::TouchNGoRedirect(_) + | WalletData::WeChatPayRedirect(_) + | WalletData::WeChatPayQr(_) + | WalletData::CashappQr(_) + | WalletData::SwishQr(_) + | WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("nexinets"), ))?, } diff --git a/crates/hyperswitch_connectors/src/connectors/nexixpay.rs b/crates/hyperswitch_connectors/src/connectors/nexixpay.rs index b660445f3ca6..9ec2ac73903b 100644 --- a/crates/hyperswitch_connectors/src/connectors/nexixpay.rs +++ b/crates/hyperswitch_connectors/src/connectors/nexixpay.rs @@ -1,5 +1,7 @@ pub mod transformers; +use std::collections::HashSet; +use common_enums::enums; use common_utils::{ errors::CustomResult, ext_traits::BytesExt, @@ -8,35 +10,49 @@ use common_utils::{ }; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::{ access_token_auth::AccessTokenAuth, - payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + payments::{ + Authorize, Capture, CompleteAuthorize, PSync, PaymentMethodToken, PreProcessing, + Session, SetupMandate, Void, + }, refunds::{Execute, RSync}, }, router_request_types::{ - AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, - PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, - RefundsData, SetupMandateRequestData, + AccessTokenRequestData, CompleteAuthorizeData, PaymentMethodTokenizationData, + PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsPreProcessingData, + PaymentsSessionData, PaymentsSyncData, RefundsData, SetupMandateRequestData, }, router_response_types::{PaymentsResponseData, RefundsResponseData}, types::{ - PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, - RefundSyncRouterData, RefundsRouterData, + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsCompleteAuthorizeRouterData, PaymentsPreProcessingRouterData, + PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, }, }; use hyperswitch_interfaces::{ - api::{self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorValidation}, + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, configs::Connectors, - errors, + consts, errors, events::connector_api_logs::ConnectorEvent, types::{self, Response}, webhooks, }; use masking::{ExposeInterface, Mask}; +use serde_json::Value; use transformers as nexixpay; +use uuid::Uuid; -use crate::{constants::headers, types::ResponseRouterData, utils}; +use crate::{ + constants::headers, + types::ResponseRouterData, + utils::{self, PaymentMethodDataType, RefundsRequestData}, +}; #[derive(Clone)] pub struct Nexixpay { @@ -52,6 +68,7 @@ impl Nexixpay { } impl api::Payment for Nexixpay {} +impl api::PaymentsPreProcessing for Nexixpay {} impl api::PaymentSession for Nexixpay {} impl api::ConnectorAccessToken for Nexixpay {} impl api::MandateSetup for Nexixpay {} @@ -63,6 +80,7 @@ impl api::Refund for Nexixpay {} impl api::RefundExecute for Nexixpay {} impl api::RefundSync for Nexixpay {} impl api::PaymentToken for Nexixpay {} +impl api::PaymentsCompleteAuthorize for Nexixpay {} impl ConnectorIntegration for Nexixpay @@ -95,10 +113,7 @@ impl ConnectorCommon for Nexixpay { } fn get_currency_unit(&self) -> api::CurrencyUnit { - api::CurrencyUnit::Base - // TODO! Check connector documentation, on which unit they are processing the currency. - // If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor, - // if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base + api::CurrencyUnit::Minor } fn common_get_content_type(&self) -> &'static str { @@ -115,10 +130,16 @@ impl ConnectorCommon for Nexixpay { ) -> CustomResult)>, errors::ConnectorError> { let auth = nexixpay::NexixpayAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; - Ok(vec![( - headers::AUTHORIZATION.to_string(), - auth.api_key.expose().into_masked(), - )]) + Ok(vec![ + ( + headers::X_API_KEY.to_string(), + auth.api_key.expose().into_masked(), + ), + ( + headers::CORRELATION_ID.to_string(), + Uuid::new_v4().to_string().into_masked(), + ), + ]) } fn build_error_response( @@ -126,19 +147,56 @@ impl ConnectorCommon for Nexixpay { res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { - let response: nexixpay::NexixpayErrorResponse = res - .response - .parse_struct("NexixpayErrorResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + let response: nexixpay::NexixpayErrorResponse = match res.status_code { + 401 => nexixpay::NexixpayErrorResponse { + errors: vec![nexixpay::NexixpayErrorBody { + code: Some(consts::NO_ERROR_CODE.to_string()), + description: Some("UNAUTHORIZED".to_string()), + }], + }, + 404 => nexixpay::NexixpayErrorResponse { + errors: vec![nexixpay::NexixpayErrorBody { + code: Some(consts::NO_ERROR_CODE.to_string()), + description: Some("NOT FOUND".to_string()), + }], + }, + _ => res + .response + .parse_struct("NexixpayErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?, + }; + + let concatenated_descriptions: Option = { + let descriptions: Vec = response + .errors + .iter() + .filter_map(|error| error.description.as_ref()) + .cloned() + .collect(); + + if descriptions.is_empty() { + None + } else { + Some(descriptions.join(", ")) + } + }; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); Ok(ErrorResponse { status_code: res.status_code, - code: response.code, - message: response.message, - reason: response.reason, + code: response + .errors + .first() + .and_then(|error| error.code.clone()) + .unwrap_or(consts::NO_ERROR_CODE.to_string()), + message: response + .errors + .first() + .and_then(|error| error.description.clone()) + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + reason: concatenated_descriptions, attempt_status: None, connector_transaction_id: None, }) @@ -146,12 +204,34 @@ impl ConnectorCommon for Nexixpay { } impl ConnectorValidation for Nexixpay { - //TODO: implement functions when support enabled + fn validate_connector_against_payment_request( + &self, + capture_method: Option, + _payment_method: enums::PaymentMethod, + _pmt: Option, + ) -> CustomResult<(), errors::ConnectorError> { + let capture_method = capture_method.unwrap_or_default(); + match capture_method { + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), + enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( + utils::construct_not_implemented_error_report(capture_method, self.id()), + ), + } + } + fn validate_mandate_payment( + &self, + pm_type: Option, + pm_data: PaymentMethodData, + ) -> CustomResult<(), errors::ConnectorError> { + let mandate_supported_pmd: HashSet = + HashSet::from([PaymentMethodDataType::Card]); + utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) + } } -impl ConnectorIntegration for Nexixpay { - //TODO: implement sessions flow -} +impl ConnectorIntegration for Nexixpay {} impl ConnectorIntegration for Nexixpay {} @@ -160,6 +240,183 @@ impl ConnectorIntegration + for Nexixpay +{ + fn get_headers( + &self, + req: &PaymentsPreProcessingRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsPreProcessingRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!( + "{}/orders/3steps/validation", + self.base_url(connectors) + )) + } + + fn get_request_body( + &self, + req: &PaymentsPreProcessingRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = nexixpay::NexixpayPreProcessingRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsPreProcessingRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsPreProcessingType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsPreProcessingType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsPreProcessingType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsPreProcessingRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: nexixpay::NexixpayPreProcessingResponse = res + .response + .parse_struct("NexixpayPreProcessingResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration + for Nexixpay +{ + fn get_headers( + &self, + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!( + "{}/orders/3steps/payment", + self.base_url(connectors) + )) + } + + fn get_request_body( + &self, + req: &PaymentsCompleteAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + let connector_router_data = nexixpay::NexixpayRouterData::from((amount, req)); + let connector_req = + nexixpay::NexixpayCompleteAuthorizeRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsCompleteAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsCompleteAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCompleteAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: nexixpay::NexixpayCompleteAuthorizeResponse = res + .response + .parse_struct("NexixpayCompleteAuthorizeResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + impl ConnectorIntegration for Nexixpay { fn get_headers( &self, @@ -175,10 +432,14 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + if req.request.off_session == Some(true) { + Ok(format!("{}/orders/mit", self.base_url(connectors))) + } else { + Ok(format!("{}/orders/3steps/init", self.base_url(connectors))) + } } fn get_request_body( @@ -227,7 +488,7 @@ impl ConnectorIntegration CustomResult { let response: nexixpay::NexixpayPaymentsResponse = res .response - .parse_struct("Nexixpay PaymentsAuthorizeResponse") + .parse_struct("NexixpayPaymentsResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); @@ -262,10 +523,15 @@ impl ConnectorIntegration for Nex fn get_url( &self, - _req: &PaymentsSyncRouterData, - _connectors: &Connectors, + req: &PaymentsSyncRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_payment_id = get_payment_id((req.request.connector_meta.clone(), None))?; + Ok(format!( + "{}/operations/{}", + self.base_url(connectors), + connector_payment_id + )) } fn build_request( @@ -289,9 +555,9 @@ impl ConnectorIntegration for Nex event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: nexixpay::NexixpayPaymentsResponse = res + let response: nexixpay::NexixpayTransactionResponse = res .response - .parse_struct("nexixpay PaymentsSyncResponse") + .parse_struct("NexixpayTransactionResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); @@ -311,13 +577,42 @@ impl ConnectorIntegration for Nex } } +fn get_payment_id( + (metadata, payment_intent): (Option, Option), +) -> CustomResult { + let connector_metadata = metadata.ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "connector_meta", + })?; + let nexixpay_meta_data = + serde_json::from_value::(connector_metadata) + .change_context(errors::ConnectorError::ParsingFailed)?; + let payment_flow = payment_intent.unwrap_or(nexixpay_meta_data.psync_flow); + let payment_id = match payment_flow { + nexixpay::NexixpayPaymentIntent::Cancel => nexixpay_meta_data.cancel_operation_id, + nexixpay::NexixpayPaymentIntent::Capture => nexixpay_meta_data.capture_operation_id, + nexixpay::NexixpayPaymentIntent::Authorize => nexixpay_meta_data.authorization_operation_id, + }; + payment_id.ok_or_else(|| { + errors::ConnectorError::MissingRequiredField { + field_name: "operation_id", + } + .into() + }) +} + impl ConnectorIntegration for Nexixpay { fn get_headers( &self, req: &PaymentsCaptureRouterData, - connectors: &Connectors, + _connectors: &Connectors, ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) + let mut header = vec![( + headers::IDEMPOTENCY_KEY.to_string(), + Uuid::new_v4().to_string().into_masked(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) } fn get_content_type(&self) -> &'static str { @@ -326,18 +621,34 @@ impl ConnectorIntegration fo fn get_url( &self, - _req: &PaymentsCaptureRouterData, - _connectors: &Connectors, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_payment_id = get_payment_id(( + req.request.connector_meta.clone(), + Some(nexixpay::NexixpayPaymentIntent::Authorize), + ))?; + Ok(format!( + "{}/operations/{}/captures", + self.base_url(connectors), + connector_payment_id + )) } fn get_request_body( &self, - _req: &PaymentsCaptureRouterData, + req: &PaymentsCaptureRouterData, _connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount_to_capture, + req.request.currency, + )?; + let connector_router_data = nexixpay::NexixpayRouterData::from((amount, req)); + let connector_req = + nexixpay::NexixpayPaymentsCaptureRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -366,9 +677,9 @@ impl ConnectorIntegration fo event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: nexixpay::NexixpayPaymentsResponse = res + let response: nexixpay::NexixpayOperationResponse = res .response - .parse_struct("Nexixpay PaymentsCaptureResponse") + .parse_struct("NexixpayOperationResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); @@ -388,15 +699,125 @@ impl ConnectorIntegration fo } } -impl ConnectorIntegration for Nexixpay {} +impl ConnectorIntegration for Nexixpay { + fn get_headers( + &self, + req: &PaymentsCancelRouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + headers::IDEMPOTENCY_KEY.to_string(), + Uuid::new_v4().to_string().into_masked(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult { + let connector_payment_id = get_payment_id(( + req.request.connector_meta.clone(), + Some(nexixpay::NexixpayPaymentIntent::Authorize), + ))?; + Ok(format!( + "{}/operations/{}/refunds", + self.base_url(connectors), + connector_payment_id + )) + } + + fn get_request_body( + &self, + req: &PaymentsCancelRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let minor_amount = + req.request + .minor_amount + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "amount", + })?; + let currency = + req.request + .currency + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "currency", + })?; + let amount = utils::convert_amount(self.amount_converter, minor_amount, currency)?; + + let connector_router_data = nexixpay::NexixpayRouterData::from((amount, req)); + let connector_req = + nexixpay::NexixpayPaymentsCancelRequest::try_from(connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) + .set_body(types::PaymentsVoidType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCancelRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: nexixpay::NexixpayOperationResponse = res + .response + .parse_struct("NexixpayOperationResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} impl ConnectorIntegration for Nexixpay { fn get_headers( &self, req: &RefundsRouterData, - connectors: &Connectors, + _connectors: &Connectors, ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) + let mut header = vec![( + headers::IDEMPOTENCY_KEY.to_string(), + Uuid::new_v4().to_string().into_masked(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) } fn get_content_type(&self) -> &'static str { @@ -405,10 +826,18 @@ impl ConnectorIntegration for Nexixpa fn get_url( &self, - _req: &RefundsRouterData, - _connectors: &Connectors, + req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_payment_id = get_payment_id(( + req.request.connector_metadata.clone(), + Some(nexixpay::NexixpayPaymentIntent::Capture), + ))?; + Ok(format!( + "{}/operations/{}/refunds", + self.base_url(connectors), + connector_payment_id + )) } fn get_request_body( @@ -454,7 +883,7 @@ impl ConnectorIntegration for Nexixpa ) -> CustomResult, errors::ConnectorError> { let response: nexixpay::RefundResponse = res .response - .parse_struct("nexixpay RefundResponse") + .parse_struct("RefundResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); @@ -489,10 +918,15 @@ impl ConnectorIntegration for Nexixpay fn get_url( &self, - _req: &RefundSyncRouterData, - _connectors: &Connectors, + req: &RefundSyncRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_refund_id = req.request.get_connector_refund_id()?; + Ok(format!( + "{}/operations/{}", + self.base_url(connectors), + connector_refund_id + )) } fn build_request( @@ -519,9 +953,9 @@ impl ConnectorIntegration for Nexixpay event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: nexixpay::RefundResponse = res + let response: nexixpay::NexixpayRSyncResponse = res .response - .parse_struct("nexixpay RefundSyncResponse") + .parse_struct("NexixpayRSyncResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); @@ -564,3 +998,5 @@ impl webhooks::IncomingWebhook for Nexixpay { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for Nexixpay {} diff --git a/crates/hyperswitch_connectors/src/connectors/nexixpay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/nexixpay/transformers.rs index f0bcb1f249e6..1384102faf8d 100644 --- a/crates/hyperswitch_connectors/src/connectors/nexixpay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/nexixpay/transformers.rs @@ -1,31 +1,47 @@ -use common_enums::enums; -use common_utils::types::StringMinorUnit; +use std::collections::HashMap; + +use cards::CardNumber; +use common_enums::{enums, AttemptStatus, CaptureMethod, Currency, RefundStatus}; +use common_utils::{ + errors::CustomResult, ext_traits::ValueExt, request::Method, types::StringMinorUnit, +}; +use error_stack::ResultExt; use hyperswitch_domain_models::{ payment_method_data::PaymentMethodData, router_data::{ConnectorAuthType, RouterData}, router_flow_types::refunds::{Execute, RSync}, - router_request_types::ResponseId, - router_response_types::{PaymentsResponseData, RefundsResponseData}, - types::{PaymentsAuthorizeRouterData, RefundsRouterData}, + router_request_types::{ + CompleteAuthorizeData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, + PaymentsPreProcessingData, PaymentsSyncData, ResponseId, + }, + router_response_types::{ + MandateReference, PaymentsResponseData, RedirectForm, RefundsResponseData, + }, + types::{ + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsCompleteAuthorizeRouterData, PaymentsPreProcessingRouterData, RefundsRouterData, + }, }; use hyperswitch_interfaces::errors; -use masking::Secret; +use masking::{ExposeInterface, Secret}; use serde::{Deserialize, Serialize}; use crate::{ types::{RefundsResponseRouterData, ResponseRouterData}, - utils::PaymentsAuthorizeRequestData, + utils::{ + get_unimplemented_payment_method_error_message, to_connector_meta, + to_connector_meta_from_secret, CardData, PaymentsAuthorizeRequestData, + PaymentsCompleteAuthorizeRequestData, PaymentsPreProcessingRequestData, RouterData as _, + }, }; -//TODO: Fill the struct with respective fields pub struct NexixpayRouterData { - pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub amount: StringMinorUnit, pub router_data: T, } impl From<(StringMinorUnit, T)> for NexixpayRouterData { fn from((amount, item): (StringMinorUnit, T)) -> Self { - //Todo : use utils to convert the amount to the type of amount that a connector accepts Self { amount, router_data: item, @@ -33,20 +49,395 @@ impl From<(StringMinorUnit, T)> for NexixpayRouterData { } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Serialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum NexixpayRecurringAction { + NoRecurring, + SubsequentPayment, + ContractCreation, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum ContractType { + MitUnscheduled, + MitScheduled, + Cit, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RecurrenceRequest { + action: NexixpayRecurringAction, + contract_id: Secret, + contract_type: ContractType, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NexixpayNonMandatePaymentRequest { + card: NexixpayCard, + recurrence: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NexixpayMandatePaymentRequest { + contract_id: Secret, + capture_type: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[serde(untagged)] +pub enum NexixpayPaymentsRequestData { + NexixpayNonMandatePaymentRequest(Box), + NexixpayMandatePaymentRequest(Box), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct NexixpayPaymentsRequest { - amount: StringMinorUnit, + order: Order, + #[serde(flatten)] + payment_data: NexixpayPaymentsRequestData, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum NexixpayCaptureType { + Implicit, + Explicit, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NexixpayCompleteAuthorizeRequest { + order: Order, card: NexixpayCard, + operation_id: String, + capture_type: Option, + three_d_s_auth_data: ThreeDSAuthData, + recurrence: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct OperationData { + operation_id: String, + operation_currency: Currency, + operation_result: NexixpayPaymentStatus, + operation_type: NexixpayOperationType, + order_id: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NexixpayCompleteAuthorizeResponse { + operation: OperationData, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NexixpayPreProcessingRequest { + operation_id: Option, + three_d_s_auth_response: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Order { + order_id: String, + amount: StringMinorUnit, + currency: Currency, + description: Option, + customer_info: CustomerInfo, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CustomerInfo { + card_holder_name: Secret, + billing_address: BillingAddress, + shipping_address: Option, } -#[derive(Default, Debug, Serialize, Eq, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BillingAddress { + name: Secret, + street: Secret, + city: String, + post_code: Secret, + country: enums::CountryAlpha2, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ShippingAddress { + name: Option>, + street: Option>, + city: Option, + post_code: Option>, + country: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct NexixpayCard { - number: cards::CardNumber, - expiry_month: Secret, - expiry_year: Secret, - cvc: Secret, - complete: bool, + pan: CardNumber, + expiry_date: Secret, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct Recurrence { + action: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PaymentsResponse { + operation: Operation, + three_d_s_auth_request: String, + three_d_s_auth_url: Secret, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NexixpayMandateResponse { + operation: Operation, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[serde(untagged)] +pub enum NexixpayPaymentsResponse { + PaymentResponse(Box), + MandateResponse(Box), +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ThreeDSAuthResult { + authentication_value: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub enum NexixpayPaymentIntent { + Capture, + Cancel, + Authorize, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NexixpayRedirectionRequest { + pub three_d_s_auth_url: String, + pub three_ds_request: String, + pub return_url: String, + pub transaction_id: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NexixpayConnectorMetaData { + pub three_d_s_auth_result: Option, + pub three_d_s_auth_response: Option>, + pub authorization_operation_id: Option, + pub capture_operation_id: Option, + pub cancel_operation_id: Option, + pub psync_flow: NexixpayPaymentIntent, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UpdateNexixpayConnectorMetaData { + pub three_d_s_auth_result: Option, + pub three_d_s_auth_response: Option>, + pub authorization_operation_id: Option, + pub capture_operation_id: Option, + pub cancel_operation_id: Option, + pub psync_flow: Option, + pub meta_data: serde_json::Value, + pub is_auto_capture: bool, +} + +fn update_nexi_meta_data( + update_request: UpdateNexixpayConnectorMetaData, +) -> CustomResult { + let nexixpay_meta_data = + serde_json::from_value::(update_request.meta_data) + .change_context(errors::ConnectorError::ParsingFailed)?; + + Ok(serde_json::json!(NexixpayConnectorMetaData { + three_d_s_auth_result: nexixpay_meta_data + .three_d_s_auth_result + .or(update_request.three_d_s_auth_result), + three_d_s_auth_response: nexixpay_meta_data + .three_d_s_auth_response + .or(update_request.three_d_s_auth_response), + authorization_operation_id: nexixpay_meta_data + .authorization_operation_id + .clone() + .or(update_request.authorization_operation_id.clone()), + capture_operation_id: { + nexixpay_meta_data + .capture_operation_id + .or(if update_request.is_auto_capture { + nexixpay_meta_data + .authorization_operation_id + .or(update_request.authorization_operation_id.clone()) + } else { + update_request.capture_operation_id + }) + }, + cancel_operation_id: nexixpay_meta_data + .cancel_operation_id + .or(update_request.cancel_operation_id), + psync_flow: update_request + .psync_flow + .unwrap_or(nexixpay_meta_data.psync_flow) + })) +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ThreeDSAuthData { + three_d_s_auth_response: Option>, + authentication_value: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NexixpayPreProcessingResponse { + operation: Operation, + three_d_s_auth_result: ThreeDSAuthResult, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Operation { + additional_data: AdditionalData, + customer_info: CustomerInfo, + operation_amount: String, + operation_currency: Currency, + operation_id: String, + operation_result: NexixpayPaymentStatus, + operation_time: String, + operation_type: NexixpayOperationType, + order_id: String, + payment_method: String, + warnings: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AdditionalData { + masked_pan: String, + card_id: Secret, + card_id4: Option>, + card_expiry_date: Option>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RedirectPayload { + #[serde(rename = "PaRes")] + pa_res: Option>, + + #[serde(rename = "paymentId")] + payment_id: Option, +} + +impl TryFrom<&PaymentsPreProcessingRouterData> for NexixpayPreProcessingRequest { + type Error = error_stack::Report; + fn try_from(item: &PaymentsPreProcessingRouterData) -> Result { + let redirect_response = item.request.redirect_response.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "redirect_response", + }, + )?; + let redirect_payload = redirect_response + .payload + .ok_or(errors::ConnectorError::MissingConnectorRedirectionPayload { + field_name: "request.redirect_response.payload", + })? + .expose(); + let customer_details_encrypted: RedirectPayload = + serde_json::from_value::(redirect_payload.clone()).change_context( + errors::ConnectorError::MissingConnectorRedirectionPayload { + field_name: "redirection_payload", + }, + )?; + Ok(Self { + operation_id: customer_details_encrypted.payment_id, + three_d_s_auth_response: customer_details_encrypted.pa_res, + }) + } +} + +impl + TryFrom< + ResponseRouterData< + F, + NexixpayPreProcessingResponse, + PaymentsPreProcessingData, + PaymentsResponseData, + >, + > for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + F, + NexixpayPreProcessingResponse, + PaymentsPreProcessingData, + PaymentsResponseData, + >, + ) -> Result { + let three_ds_data = item.response.three_d_s_auth_result; + let customer_details_encrypted: RedirectPayload = item + .data + .request + .redirect_response + .as_ref() + .and_then(|res| res.payload.to_owned()) + .ok_or(errors::ConnectorError::MissingConnectorRedirectionPayload { + field_name: "request.redirect_response.payload", + })? + .expose() + .parse_value("RedirectPayload") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + let is_auto_capture = item.data.request.is_auto_capture()?; + let meta_data = to_connector_meta_from_secret(item.data.request.metadata.clone())?; + let connector_metadata = Some(update_nexi_meta_data(UpdateNexixpayConnectorMetaData { + three_d_s_auth_result: Some(three_ds_data), + three_d_s_auth_response: customer_details_encrypted.pa_res, + authorization_operation_id: None, + capture_operation_id: None, + cancel_operation_id: None, + psync_flow: None, + meta_data, + is_auto_capture, + })?); + + Ok(Self { + status: AttemptStatus::from(item.response.operation.operation_result), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + item.response.operation.order_id.clone(), + ), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata, + network_txn_id: None, + connector_response_reference_id: Some(item.response.operation.order_id), + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } } impl TryFrom<&NexixpayRouterData<&PaymentsAuthorizeRouterData>> for NexixpayPaymentsRequest { @@ -54,27 +445,163 @@ impl TryFrom<&NexixpayRouterData<&PaymentsAuthorizeRouterData>> for NexixpayPaym fn try_from( item: &NexixpayRouterData<&PaymentsAuthorizeRouterData>, ) -> Result { - match item.router_data.request.payment_method_data.clone() { - PaymentMethodData::Card(req_card) => { - let card = NexixpayCard { - number: req_card.card_number, - expiry_month: req_card.card_exp_month, - expiry_year: req_card.card_exp_year, - cvc: req_card.card_cvc, - complete: item.router_data.request.is_auto_capture()?, + let billing_address_street = format!( + "{}, {}", + item.router_data.get_billing_line1()?.expose(), + item.router_data.get_billing_line2()?.expose() + ); + + let billing_address = BillingAddress { + name: item.router_data.get_billing_full_name()?, + street: Secret::new(billing_address_street), + city: item.router_data.get_billing_city()?, + post_code: item.router_data.get_billing_zip()?, + country: item.router_data.get_billing_country()?, + }; + let shipping_address_street = match ( + item.router_data.get_optional_shipping_line1(), + item.router_data.get_optional_shipping_line2(), + ) { + (Some(line1), Some(line2)) => Some(Secret::new(format!( + "{}, {}", + line1.expose(), + line2.expose() + ))), + (Some(line1), None) => Some(Secret::new(line1.expose())), + (None, Some(line2)) => Some(Secret::new(line2.expose())), + (None, None) => None, + }; + + let shipping_address = item + .router_data + .get_optional_billing() + .map(|_| ShippingAddress { + name: item.router_data.get_optional_shipping_full_name(), + street: shipping_address_street, + city: item.router_data.get_optional_shipping_city(), + post_code: item.router_data.get_optional_shipping_zip(), + country: item.router_data.get_optional_shipping_country(), + }); + let customer_info = CustomerInfo { + card_holder_name: item.router_data.get_billing_full_name()?, + billing_address: billing_address.clone(), + shipping_address: shipping_address.clone(), + }; + let order = Order { + order_id: item.router_data.connector_request_reference_id.clone(), + amount: item.amount.clone(), + currency: item.router_data.request.currency, + description: item.router_data.description.clone(), + customer_info, + }; + let payment_data = NexixpayPaymentsRequestData::try_from(item)?; + Ok(Self { + order, + payment_data, + }) + } +} + +impl TryFrom<&NexixpayRouterData<&PaymentsAuthorizeRouterData>> for NexixpayPaymentsRequestData { + type Error = error_stack::Report; + fn try_from( + item: &NexixpayRouterData<&PaymentsAuthorizeRouterData>, + ) -> Result { + match item + .router_data + .request + .mandate_id + .clone() + .and_then(|mandate_id| mandate_id.mandate_reference_id) + { + None => { + let recurrence_request_obj = if item.router_data.request.is_mandate_payment() { + let contract_id = item + .router_data + .connector_mandate_request_reference_id + .clone() + .ok_or_else(|| errors::ConnectorError::MissingRequiredField { + field_name: "connector_mandate_request_reference_id", + })?; + Some(RecurrenceRequest { + action: NexixpayRecurringAction::ContractCreation, + contract_id: Secret::new(contract_id), + contract_type: ContractType::MitUnscheduled, + }) + } else { + None }; - Ok(Self { - amount: item.amount.clone(), - card, - }) + + match item.router_data.request.payment_method_data { + PaymentMethodData::Card(ref req_card) => { + if item.router_data.is_three_ds() { + Ok(Self::NexixpayNonMandatePaymentRequest(Box::new( + NexixpayNonMandatePaymentRequest { + card: NexixpayCard { + pan: req_card.card_number.clone(), + expiry_date: req_card.get_expiry_date_as_mmyy()?, + }, + recurrence: recurrence_request_obj, + }, + ))) + } else { + Err(errors::ConnectorError::NotSupported { + message: "No threeds is not supported".to_string(), + connector: "nexixpay", + } + .into()) + } + } + PaymentMethodData::CardRedirect(_) + | PaymentMethodData::Wallet(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NetworkToken(_) => { + Err(errors::ConnectorError::NotImplemented( + get_unimplemented_payment_method_error_message("nexixpay"), + ))? + } + } + } + Some(api_models::payments::MandateReferenceId::ConnectorMandateId(mandate_data)) => { + let contract_id = Secret::new( + mandate_data + .get_connector_mandate_request_reference_id() + .ok_or(errors::ConnectorError::MissingConnectorMandateID)?, + ); + let capture_type = + get_nexixpay_capture_type(item.router_data.request.capture_method)?; + Ok(Self::NexixpayMandatePaymentRequest(Box::new( + NexixpayMandatePaymentRequest { + contract_id, + capture_type, + }, + ))) + } + Some(api_models::payments::MandateReferenceId::NetworkTokenWithNTI(_)) + | Some(api_models::payments::MandateReferenceId::NetworkMandateId(_)) => { + Err(errors::ConnectorError::NotImplemented( + get_unimplemented_payment_method_error_message("nexixpay"), + ) + .into()) } - _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), } } } -//TODO: Fill the struct with respective fields -// Auth Struct pub struct NexixpayAuthType { pub(super) api_key: Secret, } @@ -90,64 +617,258 @@ impl TryFrom<&ConnectorAuthType> for NexixpayAuthType { } } } -// PaymentsResponse -//TODO: Append the remaining status flags -#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase")] + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum NexixpayPaymentStatus { - Succeeded, + Authorized, + Executed, + Declined, + DeniedByRisk, + ThreedsValidated, + ThreedsFailed, + Pending, + Canceled, + Voided, + Refunded, Failed, - #[default] - Processing, } -impl From for common_enums::AttemptStatus { +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum NexixpayOperationType { + Authorization, + Capture, + Void, + Refund, + CardVerification, + Noshow, + Incremental, + DelayCharge, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum NexixpayRefundOperationType { + Refund, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum NexixpayRefundResultStatus { + Pending, + Voided, + Refunded, + Failed, + Executed, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NexixpayTransactionResponse { + order_id: String, + operation_id: String, + operation_result: NexixpayPaymentStatus, + operation_type: NexixpayOperationType, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NexixpayRSyncResponse { + order_id: String, + operation_id: String, + operation_result: NexixpayRefundResultStatus, + operation_type: NexixpayRefundOperationType, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NexixpayPaymentsCaptureRequest { + amount: StringMinorUnit, + currency: Currency, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NexixpayPaymentsCancelRequest { + description: Option, + amount: StringMinorUnit, + currency: Currency, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NexixpayOperationResponse { + operation_id: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NexixpayRefundRequest { + pub amount: StringMinorUnit, + pub currency: Currency, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RefundResponse { + operation_id: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NexixpayErrorBody { + pub code: Option, + pub description: Option, +} +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NexixpayErrorResponse { + pub errors: Vec, +} + +impl From for AttemptStatus { fn from(item: NexixpayPaymentStatus) -> Self { match item { - NexixpayPaymentStatus::Succeeded => Self::Charged, - NexixpayPaymentStatus::Failed => Self::Failure, - NexixpayPaymentStatus::Processing => Self::Authorizing, + NexixpayPaymentStatus::Declined + | NexixpayPaymentStatus::DeniedByRisk + | NexixpayPaymentStatus::ThreedsFailed + | NexixpayPaymentStatus::Failed => Self::Failure, + NexixpayPaymentStatus::Authorized => Self::Authorized, + NexixpayPaymentStatus::ThreedsValidated => Self::AuthenticationSuccessful, + NexixpayPaymentStatus::Executed => Self::Charged, + NexixpayPaymentStatus::Pending => Self::AuthenticationPending, // this is being used in authorization calls only. + NexixpayPaymentStatus::Canceled | NexixpayPaymentStatus::Voided => Self::Voided, + NexixpayPaymentStatus::Refunded => Self::AutoRefunded, } } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct NexixpayPaymentsResponse { - status: NexixpayPaymentStatus, - id: String, +fn get_nexixpay_capture_type( + item: Option, +) -> CustomResult, errors::ConnectorError> { + match item { + Some(CaptureMethod::Manual) => Ok(Some(NexixpayCaptureType::Explicit)), + Some(CaptureMethod::Automatic) | Some(CaptureMethod::SequentialAutomatic) | None => { + Ok(Some(NexixpayCaptureType::Implicit)) + } + Some(item) => Err(errors::ConnectorError::FlowNotSupported { + flow: item.to_string(), + connector: "Nexixpay".to_string(), + } + .into()), + } } -impl TryFrom> - for RouterData +impl + TryFrom< + ResponseRouterData< + F, + NexixpayPaymentsResponse, + PaymentsAuthorizeData, + PaymentsResponseData, + >, + > for RouterData { type Error = error_stack::Report; fn try_from( - item: ResponseRouterData, + item: ResponseRouterData< + F, + NexixpayPaymentsResponse, + PaymentsAuthorizeData, + PaymentsResponseData, + >, ) -> Result { - Ok(Self { - status: common_enums::AttemptStatus::from(item.response.status), - response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(item.response.id), - redirection_data: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: None, - incremental_authorization_allowed: None, - charge_id: None, + match item.response { + NexixpayPaymentsResponse::PaymentResponse(ref response_body) => { + let complete_authorize_url = item.data.request.get_complete_authorize_url()?; + let operation_id: String = response_body.operation.operation_id.clone(); + let redirection_form = nexixpay_threeds_link(NexixpayRedirectionRequest { + three_d_s_auth_url: response_body + .three_d_s_auth_url + .clone() + .expose() + .to_string(), + three_ds_request: response_body.three_d_s_auth_request.clone(), + return_url: complete_authorize_url.clone(), + transaction_id: operation_id.clone(), + })?; + let is_auto_capture = item.data.request.is_auto_capture()?; + let connector_metadata = Some(serde_json::json!(NexixpayConnectorMetaData { + three_d_s_auth_result: None, + three_d_s_auth_response: None, + authorization_operation_id: Some(operation_id.clone()), + cancel_operation_id: None, + capture_operation_id: { + if is_auto_capture { + Some(operation_id) + } else { + None + } + }, + psync_flow: NexixpayPaymentIntent::Authorize + })); + Ok(Self { + status: AttemptStatus::from(response_body.operation.operation_result.clone()), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + response_body.operation.order_id.clone(), + ), + redirection_data: Box::new(Some(redirection_form.clone())), + mandate_reference: Box::new(Some(MandateReference { + connector_mandate_id: item + .data + .connector_mandate_request_reference_id + .clone(), + payment_method_id: None, + mandate_metadata: None, + connector_mandate_request_reference_id: None, + })), + connector_metadata, + network_txn_id: None, + connector_response_reference_id: Some( + response_body.operation.order_id.clone(), + ), + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } + NexixpayPaymentsResponse::MandateResponse(ref mandate_response) => Ok(Self { + status: AttemptStatus::from(mandate_response.operation.operation_result.clone()), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + mandate_response.operation.order_id.clone(), + ), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some( + mandate_response.operation.order_id.clone(), + ), + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data }), - ..item.data - }) + } } } -//TODO: Fill the struct with respective fields -// REFUND : -// Type definition for RefundRequest -#[derive(Default, Debug, Serialize)] -pub struct NexixpayRefundRequest { - pub amount: StringMinorUnit, +fn nexixpay_threeds_link( + request: NexixpayRedirectionRequest, +) -> CustomResult { + let mut form_fields = HashMap::::new(); + form_fields.insert(String::from("ThreeDsRequest"), request.three_ds_request); + form_fields.insert(String::from("ReturnUrl"), request.return_url); + form_fields.insert(String::from("transactionId"), request.transaction_id); + + Ok(RedirectForm::Form { + endpoint: request.three_d_s_auth_url, + method: Method::Post, + form_fields, + }) } impl TryFrom<&NexixpayRouterData<&RefundsRouterData>> for NexixpayRefundRequest { @@ -155,39 +876,23 @@ impl TryFrom<&NexixpayRouterData<&RefundsRouterData>> for NexixpayRefundRe fn try_from(item: &NexixpayRouterData<&RefundsRouterData>) -> Result { Ok(Self { amount: item.amount.to_owned(), + currency: item.router_data.request.currency, }) } } -// Type definition for Refund Response - -#[allow(dead_code)] -#[derive(Debug, Serialize, Default, Deserialize, Clone)] -pub enum RefundStatus { - Succeeded, - Failed, - #[default] - Processing, -} - -impl From for enums::RefundStatus { - fn from(item: RefundStatus) -> Self { +impl From for RefundStatus { + fn from(item: NexixpayRefundResultStatus) -> Self { match item { - RefundStatus::Succeeded => Self::Success, - RefundStatus::Failed => Self::Failure, - RefundStatus::Processing => Self::Pending, - //TODO: Review mapping + NexixpayRefundResultStatus::Voided + | NexixpayRefundResultStatus::Refunded + | NexixpayRefundResultStatus::Executed => Self::Success, + NexixpayRefundResultStatus::Pending => Self::Pending, + NexixpayRefundResultStatus::Failed => Self::Failure, } } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize)] -pub struct RefundResponse { - id: String, - status: RefundStatus, -} - impl TryFrom> for RefundsRouterData { type Error = error_stack::Report; fn try_from( @@ -195,34 +900,373 @@ impl TryFrom> for RefundsRout ) -> Result { Ok(Self { response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), + connector_refund_id: item.response.operation_id, + refund_status: RefundStatus::Pending, // Refund call do not return status in their response. }), ..item.data }) } } -impl TryFrom> for RefundsRouterData { +impl TryFrom> for RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), + connector_refund_id: item.response.operation_id, + refund_status: RefundStatus::from(item.response.operation_result), }), ..item.data }) } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] -pub struct NexixpayErrorResponse { - pub status_code: u16, - pub code: String, - pub message: String, - pub reason: Option, +impl + TryFrom< + ResponseRouterData< + F, + NexixpayCompleteAuthorizeResponse, + CompleteAuthorizeData, + PaymentsResponseData, + >, + > for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + F, + NexixpayCompleteAuthorizeResponse, + CompleteAuthorizeData, + PaymentsResponseData, + >, + ) -> Result { + let is_auto_capture = item.data.request.is_auto_capture()?; + let meta_data = to_connector_meta(item.data.request.connector_meta.clone())?; + let connector_metadata = Some(update_nexi_meta_data(UpdateNexixpayConnectorMetaData { + three_d_s_auth_result: None, + three_d_s_auth_response: None, + authorization_operation_id: Some(item.response.operation.operation_id.clone()), + capture_operation_id: None, + cancel_operation_id: None, + psync_flow: Some(NexixpayPaymentIntent::Authorize), + meta_data, + is_auto_capture, + })?); + Ok(Self { + status: AttemptStatus::from(item.response.operation.operation_result), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + item.response.operation.order_id.clone(), + ), + redirection_data: Box::new(None), + mandate_reference: Box::new(Some(MandateReference { + connector_mandate_id: item.data.connector_mandate_request_reference_id.clone(), + payment_method_id: None, + mandate_metadata: None, + connector_mandate_request_reference_id: None, + })), + connector_metadata, + network_txn_id: None, + connector_response_reference_id: Some(item.response.operation.order_id), + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } +} + +impl TryFrom<&NexixpayRouterData<&PaymentsCompleteAuthorizeRouterData>> + for NexixpayCompleteAuthorizeRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &NexixpayRouterData<&PaymentsCompleteAuthorizeRouterData>, + ) -> Result { + let payment_method_data: PaymentMethodData = + item.router_data.request.payment_method_data.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "payment_method_data", + }, + )?; + let capture_type = get_nexixpay_capture_type(item.router_data.request.capture_method)?; + + let order_id = item.router_data.connector_request_reference_id.clone(); + let amount = item.amount.clone(); + let billing_address_street = format!( + "{}, {}", + item.router_data.get_billing_line1()?.expose(), + item.router_data.get_billing_line2()?.expose() + ); + + let billing_address = BillingAddress { + name: item.router_data.get_billing_full_name()?, + street: Secret::new(billing_address_street), + city: item.router_data.get_billing_city()?, + post_code: item.router_data.get_billing_zip()?, + country: item.router_data.get_billing_country()?, + }; + let shipping_address_street = match ( + item.router_data.get_optional_shipping_line1(), + item.router_data.get_optional_shipping_line2(), + ) { + (Some(line1), Some(line2)) => Some(Secret::new(format!( + "{}, {}", + line1.expose(), + line2.expose() + ))), + (Some(line1), None) => Some(Secret::new(line1.expose())), + (None, Some(line2)) => Some(Secret::new(line2.expose())), + (None, None) => None, + }; + + let shipping_address = item + .router_data + .get_optional_billing() + .map(|_| ShippingAddress { + name: item.router_data.get_optional_shipping_full_name(), + street: shipping_address_street, + city: item.router_data.get_optional_shipping_city(), + post_code: item.router_data.get_optional_shipping_zip(), + country: item.router_data.get_optional_shipping_country(), + }); + let customer_info = CustomerInfo { + card_holder_name: item.router_data.get_billing_full_name()?, + billing_address: billing_address.clone(), + shipping_address: shipping_address.clone(), + }; + let order_data = Order { + order_id, + amount, + currency: item.router_data.request.currency, + description: item.router_data.description.clone(), + customer_info, + }; + let connector_metadata = + to_connector_meta(item.router_data.request.connector_meta.clone())?; + let nexixpay_meta_data = + serde_json::from_value::(connector_metadata) + .change_context(errors::ConnectorError::ParsingFailed)?; + let operation_id = nexixpay_meta_data.authorization_operation_id.ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "authorization_operation_id", + }, + )?; + let authentication_value = nexixpay_meta_data + .three_d_s_auth_result + .and_then(|data| data.authentication_value); + let three_d_s_auth_data = ThreeDSAuthData { + three_d_s_auth_response: nexixpay_meta_data.three_d_s_auth_response, + authentication_value, + }; + let card: Result> = + match payment_method_data { + PaymentMethodData::Card(req_card) => Ok(NexixpayCard { + pan: req_card.card_number.clone(), + expiry_date: req_card.get_expiry_date_as_mmyy()?, + }), + PaymentMethodData::CardRedirect(_) + | PaymentMethodData::Wallet(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + Err(errors::ConnectorError::NotImplemented( + get_unimplemented_payment_method_error_message("nexixpay"), + ) + .into()) + } + }; + let contract_id = Secret::new( + item.router_data + .connector_mandate_request_reference_id + .clone() + .ok_or_else(|| errors::ConnectorError::MissingRequiredField { + field_name: "connector_mandate_request_reference_id", + })?, + ); + Ok(Self { + order: order_data, + card: card?, + operation_id, + capture_type, + three_d_s_auth_data, + recurrence: Some(RecurrenceRequest { + action: NexixpayRecurringAction::ContractCreation, + contract_id, + contract_type: ContractType::MitUnscheduled, + }), + }) + } +} + +impl + TryFrom< + ResponseRouterData, + > for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + F, + NexixpayTransactionResponse, + PaymentsSyncData, + PaymentsResponseData, + >, + ) -> Result { + Ok(Self { + status: AttemptStatus::from(item.response.operation_result), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.order_id.clone()), + redirection_data: Box::new(None), + mandate_reference: Box::new(Some(MandateReference { + connector_mandate_id: item.data.connector_mandate_request_reference_id.clone(), + payment_method_id: None, + mandate_metadata: None, + connector_mandate_request_reference_id: None, + })), + connector_metadata: item.data.request.connector_meta.clone(), + network_txn_id: None, + connector_response_reference_id: Some(item.response.order_id.clone()), + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } +} + +impl TryFrom<&NexixpayRouterData<&PaymentsCaptureRouterData>> for NexixpayPaymentsCaptureRequest { + type Error = error_stack::Report; + fn try_from( + item: &NexixpayRouterData<&PaymentsCaptureRouterData>, + ) -> Result { + Ok(Self { + amount: item.amount.clone(), + currency: item.router_data.request.currency, + }) + } +} + +impl + TryFrom< + ResponseRouterData, + > for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + F, + NexixpayOperationResponse, + PaymentsCaptureData, + PaymentsResponseData, + >, + ) -> Result { + let meta_data = to_connector_meta(item.data.request.connector_meta.clone())?; + let connector_metadata = Some(update_nexi_meta_data(UpdateNexixpayConnectorMetaData { + three_d_s_auth_result: None, + three_d_s_auth_response: None, + authorization_operation_id: None, + capture_operation_id: Some(item.response.operation_id.clone()), + cancel_operation_id: None, + psync_flow: Some(NexixpayPaymentIntent::Capture), + meta_data, + is_auto_capture: false, + })?); + Ok(Self { + status: AttemptStatus::Pending, // Capture call do not return status in their response. + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + item.data.request.connector_transaction_id.clone(), + ), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata, + network_txn_id: None, + connector_response_reference_id: Some( + item.data.request.connector_transaction_id.clone(), + ), + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } +} + +impl TryFrom> for NexixpayPaymentsCancelRequest { + type Error = error_stack::Report; + fn try_from(item: NexixpayRouterData<&PaymentsCancelRouterData>) -> Result { + let description = item.router_data.request.cancellation_reason.clone(); + let currency = item.router_data.request.currency.ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "currency", + }, + )?; + Ok(Self { + amount: item.amount, + currency, + description, + }) + } +} + +impl + TryFrom< + ResponseRouterData, + > for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + F, + NexixpayOperationResponse, + PaymentsCancelData, + PaymentsResponseData, + >, + ) -> Result { + let meta_data = to_connector_meta(item.data.request.connector_meta.clone())?; + let connector_metadata = Some(update_nexi_meta_data(UpdateNexixpayConnectorMetaData { + three_d_s_auth_result: None, + three_d_s_auth_response: None, + authorization_operation_id: None, + capture_operation_id: None, + cancel_operation_id: Some(item.response.operation_id.clone()), + psync_flow: Some(NexixpayPaymentIntent::Cancel), + meta_data, + is_auto_capture: false, + })?); + Ok(Self { + status: AttemptStatus::Pending, // Cancel call do not return status in their response. + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + item.data.request.connector_transaction_id.clone(), + ), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata, + network_txn_id: None, + connector_response_reference_id: Some( + item.data.request.connector_transaction_id.clone(), + ), + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } } diff --git a/crates/hyperswitch_connectors/src/connectors/nomupay.rs b/crates/hyperswitch_connectors/src/connectors/nomupay.rs new file mode 100644 index 000000000000..9ed1e501cf58 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/nomupay.rs @@ -0,0 +1,568 @@ +pub mod transformers; + +use common_utils::{ + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, +}; +use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::{ + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, + RefundSyncRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks, +}; +use masking::{ExposeInterface, Mask}; +use transformers as nomupay; + +use crate::{constants::headers, types::ResponseRouterData, utils}; + +#[derive(Clone)] +pub struct Nomupay { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl Nomupay { + pub fn new() -> &'static Self { + &Self { + amount_converter: &StringMinorUnitForConnector, + } + } +} + +impl api::Payment for Nomupay {} +impl api::PaymentSession for Nomupay {} +impl api::ConnectorAccessToken for Nomupay {} +impl api::MandateSetup for Nomupay {} +impl api::PaymentAuthorize for Nomupay {} +impl api::PaymentSync for Nomupay {} +impl api::PaymentCapture for Nomupay {} +impl api::PaymentVoid for Nomupay {} +impl api::Refund for Nomupay {} +impl api::RefundExecute for Nomupay {} +impl api::RefundSync for Nomupay {} +impl api::PaymentToken for Nomupay {} + +impl ConnectorIntegration + for Nomupay +{ + // Not Implemented (R) +} + +impl ConnectorCommonExt for Nomupay +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } +} + +impl ConnectorCommon for Nomupay { + fn id(&self) -> &'static str { + "nomupay" + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Base + // TODO! Check connector documentation, on which unit they are processing the currency. + // If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor, + // if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { + connectors.nomupay.base_url.as_ref() + } + + fn get_auth_header( + &self, + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + let auth = nomupay::NomupayAuthType::try_from(auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + Ok(vec![( + headers::AUTHORIZATION.to_string(), + auth.api_key.expose().into_masked(), + )]) + } + + fn build_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: nomupay::NomupayErrorResponse = res + .response + .parse_struct("NomupayErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.code, + message: response.message, + reason: response.reason, + attempt_status: None, + connector_transaction_id: None, + }) + } +} + +impl ConnectorValidation for Nomupay { + //TODO: implement functions when support enabled +} + +impl ConnectorIntegration for Nomupay { + //TODO: implement sessions flow +} + +impl ConnectorIntegration for Nomupay {} + +impl ConnectorIntegration for Nomupay {} + +impl ConnectorIntegration for Nomupay { + fn get_headers( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + + let connector_router_data = nomupay::NomupayRouterData::from((amount, req)); + let connector_req = nomupay::NomupayPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: nomupay::NomupayPaymentsResponse = res + .response + .parse_struct("Nomupay PaymentsAuthorizeResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Nomupay { + fn get_headers( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: nomupay::NomupayPaymentsResponse = res + .response + .parse_struct("nomupay PaymentsSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Nomupay { + fn get_headers( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsCaptureType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCaptureType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCaptureRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: nomupay::NomupayPaymentsResponse = res + .response + .parse_struct("Nomupay PaymentsCaptureResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Nomupay {} + +impl ConnectorIntegration for Nomupay { + fn get_headers( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let refund_amount = utils::convert_amount( + self.amount_converter, + req.request.minor_refund_amount, + req.request.currency, + )?; + + let connector_router_data = nomupay::NomupayRouterData::from((refund_amount, req)); + let connector_req = nomupay::NomupayRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundExecuteType::get_headers( + self, req, connectors, + )?) + .set_body(types::RefundExecuteType::get_request_body( + self, req, connectors, + )?) + .build(); + Ok(Some(request)) + } + + fn handle_response( + &self, + data: &RefundsRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult, errors::ConnectorError> { + let response: nomupay::RefundResponse = res + .response + .parse_struct("nomupay RefundResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Nomupay { + fn get_headers( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &RefundSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::RefundSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundSyncType::get_headers(self, req, connectors)?) + .set_body(types::RefundSyncType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &RefundSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: nomupay::RefundResponse = res + .response + .parse_struct("nomupay RefundSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +#[async_trait::async_trait] +impl webhooks::IncomingWebhook for Nomupay { + fn get_webhook_object_reference_id( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_event_type( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_resource_object( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } +} + +impl ConnectorSpecifications for Nomupay {} diff --git a/crates/hyperswitch_connectors/src/connectors/nomupay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/nomupay/transformers.rs new file mode 100644 index 000000000000..bea8b607daea --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/nomupay/transformers.rs @@ -0,0 +1,228 @@ +use common_enums::enums; +use common_utils::types::StringMinorUnit; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{ConnectorAuthType, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{PaymentsAuthorizeRouterData, RefundsRouterData}, +}; +use hyperswitch_interfaces::errors; +use masking::Secret; +use serde::{Deserialize, Serialize}; + +use crate::{ + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::PaymentsAuthorizeRequestData, +}; + +//TODO: Fill the struct with respective fields +pub struct NomupayRouterData { + pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub router_data: T, +} + +impl From<(StringMinorUnit, T)> for NomupayRouterData { + fn from((amount, item): (StringMinorUnit, T)) -> Self { + //Todo : use utils to convert the amount to the type of amount that a connector accepts + Self { + amount, + router_data: item, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, PartialEq)] +pub struct NomupayPaymentsRequest { + amount: StringMinorUnit, + card: NomupayCard, +} + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct NomupayCard { + number: cards::CardNumber, + expiry_month: Secret, + expiry_year: Secret, + cvc: Secret, + complete: bool, +} + +impl TryFrom<&NomupayRouterData<&PaymentsAuthorizeRouterData>> for NomupayPaymentsRequest { + type Error = error_stack::Report; + fn try_from( + item: &NomupayRouterData<&PaymentsAuthorizeRouterData>, + ) -> Result { + match item.router_data.request.payment_method_data.clone() { + PaymentMethodData::Card(req_card) => { + let card = NomupayCard { + number: req_card.card_number, + expiry_month: req_card.card_exp_month, + expiry_year: req_card.card_exp_year, + cvc: req_card.card_cvc, + complete: item.router_data.request.is_auto_capture()?, + }; + Ok(Self { + amount: item.amount.clone(), + card, + }) + } + _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), + } + } +} + +//TODO: Fill the struct with respective fields +// Auth Struct +pub struct NomupayAuthType { + pub(super) api_key: Secret, +} + +impl TryFrom<&ConnectorAuthType> for NomupayAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &ConnectorAuthType) -> Result { + match auth_type { + ConnectorAuthType::HeaderKey { api_key } => Ok(Self { + api_key: api_key.to_owned(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} +// PaymentsResponse +//TODO: Append the remaining status flags +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum NomupayPaymentStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for common_enums::AttemptStatus { + fn from(item: NomupayPaymentStatus) -> Self { + match item { + NomupayPaymentStatus::Succeeded => Self::Charged, + NomupayPaymentStatus::Failed => Self::Failure, + NomupayPaymentStatus::Processing => Self::Authorizing, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct NomupayPaymentsResponse { + status: NomupayPaymentStatus, + id: String, +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + Ok(Self { + status: common_enums::AttemptStatus::from(item.response.status), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +// REFUND : +// Type definition for RefundRequest +#[derive(Default, Debug, Serialize)] +pub struct NomupayRefundRequest { + pub amount: StringMinorUnit, +} + +impl TryFrom<&NomupayRouterData<&RefundsRouterData>> for NomupayRefundRequest { + type Error = error_stack::Report; + fn try_from(item: &NomupayRouterData<&RefundsRouterData>) -> Result { + Ok(Self { + amount: item.amount.to_owned(), + }) + } +} + +// Type definition for Refund Response + +#[allow(dead_code)] +#[derive(Debug, Serialize, Default, Deserialize, Clone)] +pub enum RefundStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for enums::RefundStatus { + fn from(item: RefundStatus) -> Self { + match item { + RefundStatus::Succeeded => Self::Success, + RefundStatus::Failed => Self::Failure, + RefundStatus::Processing => Self::Pending, + //TODO: Review mapping + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct RefundResponse { + id: String, + status: RefundStatus, +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct NomupayErrorResponse { + pub status_code: u16, + pub code: String, + pub message: String, + pub reason: Option, +} diff --git a/crates/hyperswitch_connectors/src/connectors/novalnet.rs b/crates/hyperswitch_connectors/src/connectors/novalnet.rs index dadf066a9a58..99da9c5caced 100644 --- a/crates/hyperswitch_connectors/src/connectors/novalnet.rs +++ b/crates/hyperswitch_connectors/src/connectors/novalnet.rs @@ -1,15 +1,17 @@ pub mod transformers; +use core::str; use std::collections::HashSet; use base64::Engine; use common_enums::enums; use common_utils::{ + crypto, errors::CustomResult, - ext_traits::BytesExt, + ext_traits::{ByteSliceExt, BytesExt}, request::{Method, Request, RequestBuilder, RequestContent}, types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, }; -use error_stack::{report, ResultExt}; +use error_stack::ResultExt; use hyperswitch_domain_models::{ payment_method_data::PaymentMethodData, router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, @@ -32,10 +34,10 @@ use hyperswitch_domain_models::{ use hyperswitch_interfaces::{ api::{ self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorRedirectResponse, - ConnectorValidation, + ConnectorSpecifications, ConnectorValidation, }, configs::Connectors, - errors, + disputes, errors, events::connector_api_logs::ConnectorEvent, types::{self, Response}, webhooks, @@ -156,14 +158,17 @@ impl ConnectorCommon for Novalnet { } impl ConnectorValidation for Novalnet { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( utils::construct_not_implemented_error_report(capture_method, self.id()), ), @@ -193,8 +198,12 @@ impl ConnectorValidation for Novalnet { pm_type: Option, pm_data: PaymentMethodData, ) -> CustomResult<(), errors::ConnectorError> { - let mandate_supported_pmd: HashSet = - HashSet::from([PaymentMethodDataType::Card]); + let mandate_supported_pmd: HashSet = HashSet::from([ + PaymentMethodDataType::Card, + PaymentMethodDataType::GooglePay, + PaymentMethodDataType::PaypalRedirect, + PaymentMethodDataType::ApplePay, + ]); utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) } } @@ -719,26 +728,198 @@ impl ConnectorIntegration for No } } +fn get_webhook_object_from_body( + body: &[u8], +) -> CustomResult { + let novalnet_webhook_notification_response = body + .parse_struct("NovalnetWebhookNotificationResponse") + .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; + + Ok(novalnet_webhook_notification_response) +} + #[async_trait::async_trait] impl webhooks::IncomingWebhook for Novalnet { - fn get_webhook_object_reference_id( + fn get_webhook_source_verification_algorithm( &self, _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Ok(Box::new(crypto::Sha256)) + } + + fn get_webhook_source_verification_signature( + &self, + request: &webhooks::IncomingWebhookRequestDetails<'_>, + _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, + ) -> CustomResult, errors::ConnectorError> { + let notif_item = get_webhook_object_from_body(request.body) + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; + + hex::decode(notif_item.event.checksum) + .change_context(errors::ConnectorError::WebhookVerificationSecretInvalid) + } + + fn get_webhook_source_verification_message( + &self, + request: &webhooks::IncomingWebhookRequestDetails<'_>, + _merchant_id: &common_utils::id_type::MerchantId, + connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, + ) -> CustomResult, errors::ConnectorError> { + let notif = get_webhook_object_from_body(request.body) + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; + let (amount, currency) = match notif.transaction { + novalnet::NovalnetWebhookTransactionData::CaptureTransactionData(data) => { + (data.amount, data.currency) + } + novalnet::NovalnetWebhookTransactionData::CancelTransactionData(data) => { + (data.amount, data.currency) + } + + novalnet::NovalnetWebhookTransactionData::RefundsTransactionData(data) => { + (data.amount, data.currency) + } + + novalnet::NovalnetWebhookTransactionData::SyncTransactionData(data) => { + (data.amount, data.currency) + } + }; + let amount = amount + .map(|amount| amount.to_string()) + .unwrap_or("".to_string()); + let currency = currency + .map(|amount| amount.to_string()) + .unwrap_or("".to_string()); + + let secret_auth = String::from_utf8(connector_webhook_secrets.secret.to_vec()) + .change_context(errors::ConnectorError::WebhookVerificationSecretInvalid) + .attach_printable("Could not convert webhook secret auth to UTF-8")?; + let reversed_secret_auth = novalnet::reverse_string(&secret_auth); + + let message = format!( + "{}{}{}{}{}{}", + notif.event.tid, + notif.event.event_type, + notif.result.status, + amount, + currency, + reversed_secret_auth + ); + + Ok(message.into_bytes()) + } + + fn get_webhook_object_reference_id( + &self, + request: &webhooks::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let notif = get_webhook_object_from_body(request.body) + .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; + let transaction_order_no = match notif.transaction { + novalnet::NovalnetWebhookTransactionData::CaptureTransactionData(data) => data.order_no, + novalnet::NovalnetWebhookTransactionData::CancelTransactionData(data) => data.order_no, + novalnet::NovalnetWebhookTransactionData::RefundsTransactionData(data) => data.order_no, + novalnet::NovalnetWebhookTransactionData::SyncTransactionData(data) => data.order_no, + }; + + if novalnet::is_refund_event(¬if.event.event_type) { + Ok(api_models::webhooks::ObjectReferenceId::RefundId( + api_models::webhooks::RefundIdType::ConnectorRefundId(notif.event.tid.to_string()), + )) + } else { + match transaction_order_no { + Some(order_no) => Ok(api_models::webhooks::ObjectReferenceId::PaymentId( + api_models::payments::PaymentIdType::PaymentAttemptId(order_no), + )), + None => Ok(api_models::webhooks::ObjectReferenceId::PaymentId( + api_models::payments::PaymentIdType::ConnectorTransactionId( + notif.event.tid.to_string(), + ), + )), + } + } } fn get_webhook_event_type( &self, - _request: &webhooks::IncomingWebhookRequestDetails<'_>, + request: &webhooks::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let notif = get_webhook_object_from_body(request.body) + .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?; + + let optional_transaction_status = match notif.transaction { + novalnet::NovalnetWebhookTransactionData::CaptureTransactionData(data) => { + Some(data.status) + } + novalnet::NovalnetWebhookTransactionData::CancelTransactionData(data) => data.status, + novalnet::NovalnetWebhookTransactionData::RefundsTransactionData(data) => { + Some(data.status) + } + novalnet::NovalnetWebhookTransactionData::SyncTransactionData(data) => { + Some(data.status) + } + }; + + let transaction_status = + optional_transaction_status.ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "transaction_status", + })?; + // NOTE: transaction_status will always be present for Webhooks + // But we are handling optional type here, since we are reusing TransactionData Struct from NovalnetPaymentsResponseTransactionData for Webhooks response too + // In NovalnetPaymentsResponseTransactionData, transaction_status is optional + + let incoming_webhook_event = + novalnet::get_incoming_webhook_event(notif.event.event_type, transaction_status); + Ok(incoming_webhook_event) } fn get_webhook_resource_object( &self, - _request: &webhooks::IncomingWebhookRequestDetails<'_>, + request: &webhooks::IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let notif = get_webhook_object_from_body(request.body) + .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?; + Ok(Box::new(notif)) + } + + fn get_dispute_details( + &self, + request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + let notif: transformers::NovalnetWebhookNotificationResponse = + get_webhook_object_from_body(request.body) + .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; + let (amount, currency, reason, reason_code) = match notif.transaction { + novalnet::NovalnetWebhookTransactionData::CaptureTransactionData(data) => { + (data.amount, data.currency, None, None) + } + novalnet::NovalnetWebhookTransactionData::CancelTransactionData(data) => { + (data.amount, data.currency, None, None) + } + + novalnet::NovalnetWebhookTransactionData::RefundsTransactionData(data) => { + (data.amount, data.currency, None, None) + } + + novalnet::NovalnetWebhookTransactionData::SyncTransactionData(data) => { + (data.amount, data.currency, data.reason, data.reason_code) + } + }; + + let dispute_status = + novalnet::get_novalnet_dispute_status(notif.event.event_type).to_string(); + Ok(disputes::DisputePayload { + amount: novalnet::option_to_result(amount)?.to_string(), + currency: novalnet::option_to_result(currency)?, + dispute_stage: api_models::enums::DisputeStage::Dispute, + connector_dispute_id: notif.event.tid.to_string(), + connector_reason: reason, + connector_reason_code: reason_code, + challenge_required_by: None, + connector_status: dispute_status, + created_at: None, + updated_at: None, + }) } } + +impl ConnectorSpecifications for Novalnet {} diff --git a/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs b/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs index 56da53c2873e..0a70393a13cd 100644 --- a/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs @@ -1,17 +1,14 @@ use std::collections::HashMap; +use api_models::webhooks::IncomingWebhookEvent; use cards::CardNumber; use common_enums::{enums, enums as api_enums}; use common_utils::{ - consts, - ext_traits::OptionExt, - pii::{Email, IpAddress}, - request::Method, - types::StringMinorUnit, + consts, ext_traits::OptionExt, pii::Email, request::Method, types::StringMinorUnit, }; use error_stack::ResultExt; use hyperswitch_domain_models::{ - payment_method_data::PaymentMethodData, + payment_method_data::{PaymentMethodData, WalletData as WalletDataPaymentMethod}, router_data::{ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::refunds::{Execute, RSync}, router_request_types::{PaymentsCancelData, PaymentsCaptureData, PaymentsSyncData, ResponseId}, @@ -31,9 +28,8 @@ use strum::Display; use crate::{ types::{RefundsResponseRouterData, ResponseRouterData}, utils::{ - self, BrowserInformationData, PaymentsAuthorizeRequestData, PaymentsCancelRequestData, - PaymentsCaptureRequestData, PaymentsSyncRequestData, RefundsRequestData, - RouterData as OtherRouterData, + self, ApplePay, PaymentsAuthorizeRequestData, PaymentsCancelRequestData, + PaymentsCaptureRequestData, PaymentsSyncRequestData, RefundsRequestData, RouterData as _, }, }; @@ -54,34 +50,36 @@ impl From<(StringMinorUnit, T)> for NovalnetRouterData { #[derive(Debug, Copy, Serialize, Deserialize, Clone)] pub enum NovalNetPaymentTypes { CREDITCARD, + PAYPAL, + GOOGLEPAY, + APPLEPAY, } -#[derive(Default, Debug, Serialize, PartialEq, Clone)] +#[derive(Default, Debug, Serialize, Clone)] pub struct NovalnetPaymentsRequestMerchant { signature: Secret, tariff: Secret, } -#[derive(Default, Debug, Serialize, PartialEq, Clone)] +#[derive(Default, Debug, Serialize, Clone)] pub struct NovalnetPaymentsRequestBilling { - house_no: Secret, - street: Secret, - city: Secret, - zip: Secret, - country_code: api_enums::CountryAlpha2, + house_no: Option>, + street: Option>, + city: Option>, + zip: Option>, + country_code: Option, } -#[derive(Default, Debug, Serialize, PartialEq, Clone)] +#[derive(Default, Debug, Serialize, Clone)] pub struct NovalnetPaymentsRequestCustomer { first_name: Secret, last_name: Secret, email: Email, mobile: Option>, - billing: NovalnetPaymentsRequestBilling, - customer_ip: Secret, + billing: Option, + no_nc: i64, } #[derive(Default, Debug, Clone, Serialize, Deserialize)] - pub struct NovalnetCard { card_number: CardNumber, card_expiry_month: Secret, @@ -95,10 +93,22 @@ pub struct NovalnetMandate { token: Secret, } +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct NovalnetGooglePay { + wallet_data: Secret, +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct NovalnetApplePay { + wallet_data: Secret, +} + #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(untagged)] pub enum NovalNetPaymentData { - PaymentCard(NovalnetCard), + Card(NovalnetCard), + GooglePay(NovalnetGooglePay), + ApplePay(NovalnetApplePay), MandatePayment(NovalnetMandate), } @@ -114,7 +124,7 @@ pub struct NovalnetPaymentsRequestTransaction { amount: StringMinorUnit, currency: common_enums::Currency, order_no: String, - payment_data: NovalNetPaymentData, + payment_data: Option, hook_url: Option, return_url: Option, error_return_url: Option, @@ -130,6 +140,21 @@ pub struct NovalnetPaymentsRequest { custom: NovalnetCustom, } +impl TryFrom<&api_enums::PaymentMethodType> for NovalNetPaymentTypes { + type Error = error_stack::Report; + fn try_from(item: &api_enums::PaymentMethodType) -> Result { + match item { + api_enums::PaymentMethodType::ApplePay => Ok(Self::APPLEPAY), + api_enums::PaymentMethodType::Credit => Ok(Self::CREDITCARD), + api_enums::PaymentMethodType::GooglePay => Ok(Self::GOOGLEPAY), + api_enums::PaymentMethodType::Paypal => Ok(Self::PAYPAL), + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Novalnet"), + ))?, + } + } +} + impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaymentsRequest { type Error = error_stack::Report; fn try_from( @@ -152,26 +177,27 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym }; let billing = NovalnetPaymentsRequestBilling { - house_no: item.router_data.get_billing_line1()?, - street: item.router_data.get_billing_line2()?, - city: Secret::new(item.router_data.get_billing_city()?), - zip: item.router_data.get_billing_zip()?, - country_code: item.router_data.get_billing_country()?, + house_no: item.router_data.get_optional_billing_line1(), + street: item.router_data.get_optional_billing_line2(), + city: item + .router_data + .get_optional_billing_city() + .map(Secret::new), + zip: item.router_data.get_optional_billing_zip(), + country_code: item.router_data.get_optional_billing_country(), }; - let customer_ip = item - .router_data - .request - .get_browser_info()? - .get_ip_address()?; - let customer = NovalnetPaymentsRequestCustomer { first_name: item.router_data.get_billing_first_name()?, last_name: item.router_data.get_billing_last_name()?, - email: item.router_data.get_billing_email()?, + email: item + .router_data + .get_billing_email() + .or(item.router_data.request.get_email())?, mobile: item.router_data.get_optional_billing_phone_number(), - billing, - customer_ip, + billing: Some(billing), + // no_nc is used to indicate if minimal customer data is passed or not + no_nc: 1, }; let lang = item @@ -181,6 +207,12 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym .unwrap_or(consts::DEFAULT_LOCALE.to_string().to_string()); let custom = NovalnetCustom { lang }; let hook_url = item.router_data.request.get_webhook_url()?; + let return_url = item.router_data.request.get_router_return_url()?; + let create_token = if item.router_data.request.is_mandate_payment() { + Some(1) + } else { + None + }; match item .router_data @@ -191,19 +223,13 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym { None => match item.router_data.request.payment_method_data { PaymentMethodData::Card(ref req_card) => { - let novalnet_card = NovalNetPaymentData::PaymentCard(NovalnetCard { + let novalnet_card = NovalNetPaymentData::Card(NovalnetCard { card_number: req_card.card_number.clone(), card_expiry_month: req_card.card_exp_month.clone(), card_expiry_year: req_card.card_exp_year.clone(), card_cvc: req_card.card_cvc.clone(), card_holder: item.router_data.get_billing_full_name()?, }); - let create_token = if item.router_data.request.is_mandate_payment() { - Some(1) - } else { - None - }; - let return_url = item.router_data.request.get_return_url()?; let transaction = NovalnetPaymentsRequestTransaction { test_mode, @@ -214,7 +240,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym hook_url: Some(hook_url), return_url: Some(return_url.clone()), error_return_url: Some(return_url.clone()), - payment_data: novalnet_card, + payment_data: Some(novalnet_card), enforce_3d, create_token, }; @@ -226,13 +252,126 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym custom, }) } + + PaymentMethodData::Wallet(ref wallet_data) => match wallet_data { + WalletDataPaymentMethod::GooglePay(ref req_wallet) => { + let novalnet_google_pay: NovalNetPaymentData = + NovalNetPaymentData::GooglePay(NovalnetGooglePay { + wallet_data: Secret::new( + req_wallet.tokenization_data.token.clone(), + ), + }); + + let transaction = NovalnetPaymentsRequestTransaction { + test_mode, + payment_type: NovalNetPaymentTypes::GOOGLEPAY, + amount: item.amount.clone(), + currency: item.router_data.request.currency, + order_no: item.router_data.connector_request_reference_id.clone(), + hook_url: Some(hook_url), + return_url: None, + error_return_url: None, + payment_data: Some(novalnet_google_pay), + enforce_3d, + create_token, + }; + + Ok(Self { + merchant, + transaction, + customer, + custom, + }) + } + WalletDataPaymentMethod::ApplePay(payment_method_data) => { + let transaction = NovalnetPaymentsRequestTransaction { + test_mode, + payment_type: NovalNetPaymentTypes::APPLEPAY, + amount: item.amount.clone(), + currency: item.router_data.request.currency, + order_no: item.router_data.connector_request_reference_id.clone(), + hook_url: Some(hook_url), + return_url: None, + error_return_url: None, + payment_data: Some(NovalNetPaymentData::ApplePay(NovalnetApplePay { + wallet_data: payment_method_data + .get_applepay_decoded_payment_data()?, + })), + enforce_3d: None, + create_token, + }; + + Ok(Self { + merchant, + transaction, + customer, + custom, + }) + } + WalletDataPaymentMethod::AliPayQr(_) + | WalletDataPaymentMethod::AliPayRedirect(_) + | WalletDataPaymentMethod::AliPayHkRedirect(_) + | WalletDataPaymentMethod::MomoRedirect(_) + | WalletDataPaymentMethod::KakaoPayRedirect(_) + | WalletDataPaymentMethod::GoPayRedirect(_) + | WalletDataPaymentMethod::GcashRedirect(_) + | WalletDataPaymentMethod::ApplePayRedirect(_) + | WalletDataPaymentMethod::ApplePayThirdPartySdk(_) + | WalletDataPaymentMethod::DanaRedirect {} + | WalletDataPaymentMethod::GooglePayRedirect(_) + | WalletDataPaymentMethod::GooglePayThirdPartySdk(_) + | WalletDataPaymentMethod::MbWayRedirect(_) + | WalletDataPaymentMethod::MobilePayRedirect(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("novalnet"), + ) + .into()) + } + WalletDataPaymentMethod::PaypalRedirect(_) => { + let transaction = NovalnetPaymentsRequestTransaction { + test_mode, + payment_type: NovalNetPaymentTypes::PAYPAL, + amount: item.amount.clone(), + currency: item.router_data.request.currency, + order_no: item.router_data.connector_request_reference_id.clone(), + hook_url: Some(hook_url), + return_url: Some(return_url.clone()), + error_return_url: Some(return_url.clone()), + payment_data: None, + enforce_3d: None, + create_token, + }; + Ok(Self { + merchant, + transaction, + customer, + custom, + }) + } + WalletDataPaymentMethod::PaypalSdk(_) + | WalletDataPaymentMethod::Paze(_) + | WalletDataPaymentMethod::SamsungPay(_) + | WalletDataPaymentMethod::TwintRedirect {} + | WalletDataPaymentMethod::VippsRedirect {} + | WalletDataPaymentMethod::TouchNGoRedirect(_) + | WalletDataPaymentMethod::WeChatPayRedirect(_) + | WalletDataPaymentMethod::CashappQr(_) + | WalletDataPaymentMethod::SwishQr(_) + | WalletDataPaymentMethod::WeChatPayQr(_) + | WalletDataPaymentMethod::Mifinity(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("novalnet"), + ) + .into()) + } + }, _ => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("novalnet"), ) .into()), }, Some(api_models::payments::MandateReferenceId::ConnectorMandateId(mandate_data)) => { - let connector_mandate_id = mandate_data.connector_mandate_id.ok_or( + let connector_mandate_id = mandate_data.get_connector_mandate_id().ok_or( errors::ConnectorError::MissingRequiredField { field_name: "connector_mandate_id", }, @@ -242,16 +381,21 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym token: Secret::new(connector_mandate_id), }); + let payment_type = match item.router_data.request.payment_method_type { + Some(pm_type) => NovalNetPaymentTypes::try_from(&pm_type)?, + None => NovalNetPaymentTypes::CREDITCARD, + }; + let transaction = NovalnetPaymentsRequestTransaction { test_mode, - payment_type: NovalNetPaymentTypes::CREDITCARD, + payment_type, amount: item.amount.clone(), currency: item.router_data.request.currency, order_no: item.router_data.connector_request_reference_id.clone(), hook_url: Some(hook_url), return_url: None, error_return_url: None, - payment_data: novalnet_mandate_data, + payment_data: Some(novalnet_mandate_data), enforce_3d, create_token: None, }; @@ -297,7 +441,7 @@ impl TryFrom<&ConnectorAuthType> for NovalnetAuthType { } // PaymentsResponse -#[derive(Debug, Copy, Clone, Default, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Display, Copy, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum NovalnetTransactionStatus { Success, @@ -305,16 +449,15 @@ pub enum NovalnetTransactionStatus { Confirmed, OnHold, Pending, - #[default] Deactivated, Progress, } -#[derive(Debug, Copy, Display, Clone, Default, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +#[derive(Debug, Copy, Display, Clone, Serialize, Deserialize, PartialEq)] +#[strum(serialize_all = "UPPERCASE")] +#[serde(rename_all = "UPPERCASE")] pub enum NovalnetAPIStatus { Success, - #[default] Failure, } @@ -333,29 +476,35 @@ impl From for common_enums::AttemptStatus { } } -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ResultData { - redirect_url: Option>, - status: NovalnetAPIStatus, - status_code: u16, - status_text: String, - additional_message: Option, + pub redirect_url: Option>, + pub status: NovalnetAPIStatus, + pub status_code: u64, + pub status_text: String, + pub additional_message: Option, } -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct TransactionData { - payment_type: Option, - status_code: u16, - txn_secret: Option, - tid: Option>, - test_mode: Option, - status: Option, +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NovalnetPaymentsResponseTransactionData { + pub amount: Option, + pub currency: Option, + pub date: Option, + pub order_no: Option, + pub payment_data: Option, + pub payment_type: Option, + pub status_code: Option, + pub txn_secret: Option>, + pub tid: Option>, + pub test_mode: Option, + pub status: Option, + pub authorization: Option, } -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct NovalnetPaymentsResponse { result: ResultData, - transaction: Option, + transaction: Option, } pub fn get_error_response(result: ResultData, status_code: u16) -> ErrorResponse { @@ -372,6 +521,24 @@ pub fn get_error_response(result: ResultData, status_code: u16) -> ErrorResponse } } +impl NovalnetPaymentsResponseTransactionData { + pub fn get_token(transaction_data: Option<&Self>) -> Option { + if let Some(data) = transaction_data { + match &data.payment_data { + Some(NovalnetResponsePaymentData::Card(card_data)) => { + card_data.token.clone().map(|token| token.expose()) + } + Some(NovalnetResponsePaymentData::Paypal(paypal_data)) => { + paypal_data.token.clone().map(|token| token.expose()) + } + None => None, + } + } else { + None + } + } +} + impl TryFrom> for RouterData { @@ -396,18 +563,23 @@ impl TryFrom TryFrom>, pub last_name: Option>, pub mobile: Option>, + pub tel: Option>, + pub fax: Option>, } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct NovalnetResponseBilling { - pub city: Secret, - pub country_code: Secret, + pub city: Option>, + pub country_code: Option>, pub house_no: Option>, - pub street: Secret, - pub zip: Secret, + pub street: Option>, + pub zip: Option>, + pub state: Option>, } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct NovalnetResponseMerchant { - pub project: u32, - pub vendor: u32, + pub project: Option>, + pub project_name: Option>, + pub project_url: Option, + pub vendor: Option>, } #[derive(Serialize, Deserialize, Clone, Debug)] -pub struct NovalnetResponseTransactionData { - pub amount: Option, +pub struct NovalnetAuthorizationResponse { + expiry_date: Option, + auto_action: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct NovalnetSyncResponseTransactionData { + pub amount: Option, pub currency: Option, pub date: Option, pub order_no: Option, - pub payment_data: NovalnetResponsePaymentData, + pub payment_data: Option, pub payment_type: String, pub status: NovalnetTransactionStatus, - pub status_code: u16, + pub status_code: u64, pub test_mode: u8, pub tid: Option>, pub txn_secret: Option>, + pub authorization: Option, + pub reason: Option, + pub reason_code: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(untagged)] pub enum NovalnetResponsePaymentData { - PaymentCard(NovalnetResponseCard), + Card(NovalnetResponseCard), + Paypal(NovalnetResponsePaypal), } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -494,7 +688,14 @@ pub struct NovalnetResponseCard { pub card_number: Secret, pub cc_3d: Option>, pub last_four: Option>, - pub token: Option, + pub token: Option>, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct NovalnetResponsePaypal { + pub paypal_account: Option, + pub paypal_transaction_id: Option>, + pub token: Option>, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -502,7 +703,7 @@ pub struct NovalnetPSyncResponse { pub customer: Option, pub merchant: Option, pub result: ResultData, - pub transaction: Option, + pub transaction: Option, } #[derive(Debug, Copy, Serialize, Default, Deserialize, Clone)] @@ -616,27 +817,27 @@ impl From for enums::RefundStatus { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NovalnetRefundSyncResponse { result: ResultData, - transaction: Option, + transaction: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NovalnetRefundsTransactionData { - amount: u32, - date: Option, - currency: common_enums::Currency, - order_no: String, - payment_type: String, - refund: RefundData, - refunded_amount: u32, - status: NovalnetTransactionStatus, - status_code: u16, - test_mode: u8, - tid: Option>, + pub amount: Option, + pub date: Option, + pub currency: Option, + pub order_no: Option, + pub payment_type: String, + pub refund: RefundData, + pub refunded_amount: u64, + pub status: NovalnetTransactionStatus, + pub status_code: u64, + pub test_mode: u8, + pub tid: Option>, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RefundData { - amount: u32, + amount: u64, currency: common_enums::Currency, payment_type: String, tid: Option>, @@ -663,7 +864,7 @@ impl TryFrom> .response .transaction .clone() - .and_then(|data| data.tid.map(|tid| tid.expose().to_string())) + .and_then(|data| data.refund.tid.map(|tid| tid.expose().to_string())) .ok_or(errors::ConnectorError::ResponseHandlingFailed)?; let transaction_status = item @@ -691,7 +892,7 @@ impl TryFrom> } } -#[derive(Default, Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct NovolnetRedirectionResponse { status: NovalnetTransactionStatus, tid: Secret, @@ -738,11 +939,17 @@ impl TryFrom<&PaymentsSyncRouterData> for NovalnetSyncRequest { } } -impl NovalnetResponseTransactionData { +impl NovalnetSyncResponseTransactionData { pub fn get_token(transaction_data: Option<&Self>) -> Option { if let Some(data) = transaction_data { match &data.payment_data { - NovalnetResponsePaymentData::PaymentCard(card_data) => card_data.token.clone(), + Some(NovalnetResponsePaymentData::Card(card_data)) => { + card_data.token.clone().map(|token| token.expose()) + } + Some(NovalnetResponsePaymentData::Paypal(paypal_data)) => { + paypal_data.token.clone().map(|token| token.expose()) + } + None => None, } } else { None @@ -772,8 +979,9 @@ impl .clone() .map(|transaction_data| transaction_data.status) .unwrap_or(NovalnetTransactionStatus::Pending); - let mandate_reference_id = - NovalnetResponseTransactionData::get_token(item.response.transaction.as_ref()); + let mandate_reference_id = NovalnetSyncResponseTransactionData::get_token( + item.response.transaction.as_ref(), + ); Ok(Self { status: common_enums::AttemptStatus::from(transaction_status), @@ -782,14 +990,15 @@ impl .clone() .map(ResponseId::ConnectorTransactionId) .unwrap_or(ResponseId::NoResponseId), - redirection_data: None, - mandate_reference: mandate_reference_id.as_ref().map(|id| { + redirection_data: Box::new(None), + mandate_reference: Box::new(mandate_reference_id.as_ref().map(|id| { MandateReference { connector_mandate_id: Some(id.clone()), payment_method_id: None, mandate_metadata: None, + connector_mandate_request_reference_id: None, } - }), + })), connector_metadata: None, network_txn_id: None, connector_response_reference_id: transaction_id.clone(), @@ -811,31 +1020,31 @@ impl } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CaptureTransactionData { - amount: Option, - capture: CaptureData, - currency: Option, - order_no: Option, - payment_type: Option, - status: Option, - status_code: Option, - test_mode: Option, - tid: Option>, +pub struct NovalnetCaptureTransactionData { + pub amount: Option, + pub capture: CaptureData, + pub currency: Option, + pub order_no: Option, + pub payment_type: String, + pub status: NovalnetTransactionStatus, + pub status_code: Option, + pub test_mode: Option, + pub tid: Secret, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CaptureData { - amount: Option, + amount: Option, payment_type: Option, status: Option, - status_code: u16, + status_code: u64, tid: Option>, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NovalnetCaptureResponse { pub result: ResultData, - pub transaction: Option, + pub transaction: Option, } impl @@ -858,11 +1067,11 @@ impl .response .transaction .clone() - .and_then(|data| data.tid.map(|tid| tid.expose().to_string())); + .map(|data| data.tid.expose().to_string()); let transaction_status = item .response .transaction - .and_then(|transaction_data| transaction_data.status) + .map(|transaction_data| transaction_data.status) .unwrap_or(NovalnetTransactionStatus::Pending); Ok(Self { @@ -872,8 +1081,8 @@ impl .clone() .map(ResponseId::ConnectorTransactionId) .unwrap_or(ResponseId::NoResponseId), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: transaction_id.clone(), @@ -999,10 +1208,10 @@ impl TryFrom<&PaymentsCancelRouterData> for NovalnetCancelRequest { } } -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct NovalnetCancelResponse { result: ResultData, - transaction: Option, + transaction: Option, } impl @@ -1041,8 +1250,8 @@ impl .clone() .map(ResponseId::ConnectorTransactionId) .unwrap_or(ResponseId::NoResponseId), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: transaction_id.clone(), @@ -1064,10 +1273,110 @@ impl } //TODO: Fill the struct with respective fields -#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Default, Debug, Serialize, Deserialize)] pub struct NovalnetErrorResponse { - pub status_code: u16, + pub status_code: u64, pub code: String, pub message: String, pub reason: Option, } + +#[derive(Display, Debug, Serialize, Deserialize)] +#[strum(serialize_all = "SCREAMING_SNAKE_CASE")] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum WebhookEventType { + Payment, + TransactionCapture, + TransactionCancel, + TransactionRefund, + Chargeback, + Credit, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct NovalnetWebhookEvent { + pub checksum: String, + pub tid: i64, + pub parent_tid: Option, + #[serde(rename = "type")] + pub event_type: WebhookEventType, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(untagged)] +pub enum NovalnetWebhookTransactionData { + SyncTransactionData(NovalnetSyncResponseTransactionData), + CaptureTransactionData(NovalnetCaptureTransactionData), + CancelTransactionData(NovalnetPaymentsResponseTransactionData), + RefundsTransactionData(NovalnetRefundsTransactionData), +} +#[derive(Serialize, Deserialize, Debug)] +pub struct NovalnetWebhookNotificationResponse { + pub event: NovalnetWebhookEvent, + pub result: ResultData, + pub transaction: NovalnetWebhookTransactionData, +} + +pub fn is_refund_event(event_code: &WebhookEventType) -> bool { + matches!(event_code, WebhookEventType::TransactionRefund) +} + +pub fn get_incoming_webhook_event( + status: WebhookEventType, + transaction_status: NovalnetTransactionStatus, +) -> IncomingWebhookEvent { + match status { + WebhookEventType::Payment => match transaction_status { + NovalnetTransactionStatus::Confirmed | NovalnetTransactionStatus::Success => { + IncomingWebhookEvent::PaymentIntentSuccess + } + NovalnetTransactionStatus::OnHold => { + IncomingWebhookEvent::PaymentIntentAuthorizationSuccess + } + NovalnetTransactionStatus::Pending => IncomingWebhookEvent::PaymentIntentProcessing, + NovalnetTransactionStatus::Progress => IncomingWebhookEvent::EventNotSupported, + _ => IncomingWebhookEvent::PaymentIntentFailure, + }, + WebhookEventType::TransactionCapture => match transaction_status { + NovalnetTransactionStatus::Confirmed | NovalnetTransactionStatus::Success => { + IncomingWebhookEvent::PaymentIntentCaptureSuccess + } + _ => IncomingWebhookEvent::PaymentIntentCaptureFailure, + }, + WebhookEventType::TransactionCancel => match transaction_status { + NovalnetTransactionStatus::Deactivated => IncomingWebhookEvent::PaymentIntentCancelled, + _ => IncomingWebhookEvent::PaymentIntentCancelFailure, + }, + WebhookEventType::TransactionRefund => match transaction_status { + NovalnetTransactionStatus::Confirmed | NovalnetTransactionStatus::Success => { + IncomingWebhookEvent::RefundSuccess + } + _ => IncomingWebhookEvent::RefundFailure, + }, + WebhookEventType::Chargeback => IncomingWebhookEvent::DisputeOpened, + WebhookEventType::Credit => IncomingWebhookEvent::DisputeWon, + } +} + +pub fn reverse_string(s: &str) -> String { + s.chars().rev().collect() +} + +#[derive(Display, Debug, Serialize, Deserialize)] +pub enum WebhookDisputeStatus { + DisputeOpened, + DisputeWon, + Unknown, +} + +pub fn get_novalnet_dispute_status(status: WebhookEventType) -> WebhookDisputeStatus { + match status { + WebhookEventType::Chargeback => WebhookDisputeStatus::DisputeOpened, + WebhookEventType::Credit => WebhookDisputeStatus::DisputeWon, + _ => WebhookDisputeStatus::Unknown, + } +} + +pub fn option_to_result(opt: Option) -> Result { + opt.ok_or(errors::ConnectorError::WebhookBodyDecodingFailed) +} diff --git a/crates/router/src/connector/paybox.rs b/crates/hyperswitch_connectors/src/connectors/paybox.rs similarity index 63% rename from crates/router/src/connector/paybox.rs rename to crates/hyperswitch_connectors/src/connectors/paybox.rs index a251679be85b..89453364d875 100644 --- a/crates/router/src/connector/paybox.rs +++ b/crates/hyperswitch_connectors/src/connectors/paybox.rs @@ -1,34 +1,53 @@ pub mod transformers; -use common_enums::enums; -use common_utils::types::{AmountConvertor, MinorUnit, MinorUnitForConnector}; -use error_stack::{report, ResultExt}; -use masking::ExposeInterface; -use transformers as paybox; - -use super::utils::{ - RouterData, {self as connector_utils}, +use api_models::webhooks::{IncomingWebhookEvent, ObjectReferenceId}; +use common_enums::{enums, CallConnectorAction, PaymentAction}; +use common_utils::{ + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, MinorUnit, MinorUnitForConnector}, }; -use crate::{ - configs::settings, - connector::utils, - core::{ - errors::{self, CustomResult}, - payments, +use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + CompleteAuthorize, }, - events::connector_api_logs::ConnectorEvent, - headers, - services::{ - self, - request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, + router_request_types::{ + AccessTokenRequestData, CompleteAuthorizeData, PaymentMethodTokenizationData, + PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, + PaymentsSyncData, RefundsData, SetupMandateRequestData, }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, types::{ - self, - api::{self, ConnectorCommon, ConnectorCommonExt}, - ErrorResponse, RequestContent, Response, + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsCompleteAuthorizeRouterData, PaymentsSyncRouterData, RefundSyncRouterData, + RefundsRouterData, }, - utils::BytesExt, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorRedirectResponse, + ConnectorSpecifications, ConnectorValidation, + }, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, +}; +use masking::{ExposeInterface, Mask}; +use transformers as paybox; + +use crate::{ + constants::headers, + types::ResponseRouterData, + utils::{self, convert_amount, is_mandate_supported, PaymentMethodDataType, RouterData as _}, }; #[derive(Clone)] @@ -57,17 +76,18 @@ impl api::RefundExecute for Paybox {} impl api::RefundSync for Paybox {} impl api::PaymentToken for Paybox {} impl api::PaymentsCompleteAuthorize for Paybox {} -impl ConnectorIntegration - for Paybox -{ +impl ConnectorIntegration for Paybox { + fn build_request( + &self, + _req: &PaymentsCancelRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::NotImplemented("Cancel/Void flow".to_string()).into()) + } } -impl - ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Paybox +impl ConnectorIntegration + for Paybox { } @@ -77,9 +97,9 @@ where { fn build_headers( &self, - _req: &types::RouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let header = vec![( headers::CONTENT_TYPE.to_string(), self.get_content_type().to_string().into(), @@ -97,14 +117,14 @@ impl ConnectorCommon for Paybox { "application/x-www-form-urlencoded" } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.paybox.base_url.as_ref() } fn get_auth_header( &self, - auth_type: &types::ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { let auth = paybox::PayboxAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; Ok(vec![( @@ -138,47 +158,43 @@ impl ConnectorCommon for Paybox { } impl ConnectorValidation for Paybox { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( utils::construct_not_implemented_error_report(capture_method, self.id()), ), } } + fn validate_mandate_payment( + &self, + pm_type: Option, + pm_data: PaymentMethodData, + ) -> CustomResult<(), errors::ConnectorError> { + let mandate_supported_pmd = std::collections::HashSet::from([PaymentMethodDataType::Card]); + is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) + } } -impl ConnectorIntegration - for Paybox -{ -} +impl ConnectorIntegration for Paybox {} -impl ConnectorIntegration - for Paybox -{ -} +impl ConnectorIntegration for Paybox {} -impl - ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Paybox -{ -} -impl ConnectorIntegration - for Paybox -{ +impl ConnectorIntegration for Paybox {} +impl ConnectorIntegration for Paybox { fn get_headers( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -188,8 +204,8 @@ impl ConnectorIntegration CustomResult { if req.is_three_ds() { Ok(format!( @@ -203,10 +219,10 @@ impl ConnectorIntegration CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_amount, req.request.currency, @@ -219,12 +235,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsAuthorizeType::get_url( self, req, connectors, )?) @@ -238,15 +254,15 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: paybox::PayboxResponse = paybox::parse_paybox_response(res.response, data.is_three_ds())?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -262,14 +278,12 @@ impl ConnectorIntegration - for Paybox -{ +impl ConnectorIntegration for Paybox { fn get_headers( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -278,28 +292,28 @@ impl ConnectorIntegration CustomResult { let connector_req = paybox::PayboxPSyncRequest::try_from(req)?; Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn get_url( &self, - _req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, + _req: &PaymentsSyncRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(self.base_url(connectors).to_string()) } fn build_request( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .set_body(types::PaymentsSyncType::get_request_body( @@ -311,15 +325,15 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: paybox::PayboxSyncResponse = paybox::parse_url_encoded_to_struct(res.response)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -335,14 +349,12 @@ impl ConnectorIntegration - for Paybox -{ +impl ConnectorIntegration for Paybox { fn get_headers( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -352,18 +364,18 @@ impl ConnectorIntegration CustomResult { Ok(self.base_url(connectors).to_string()) } fn get_request_body( &self, - req: &types::PaymentsCaptureRouterData, - _connectors: &settings::Connectors, + req: &PaymentsCaptureRouterData, + _connectors: &Connectors, ) -> CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_amount_to_capture, req.request.currency, @@ -376,12 +388,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) .attach_default_headers() .set_body(types::PaymentsCaptureType::get_request_body( @@ -393,16 +405,16 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: paybox::PayboxCaptureResponse = paybox::parse_url_encoded_to_struct(res.response)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -418,12 +430,12 @@ impl ConnectorIntegration for Paybox { +impl ConnectorIntegration for Paybox { fn get_headers( &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -433,18 +445,18 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, + _req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(self.base_url(connectors).to_string()) } fn get_request_body( &self, - req: &types::RefundsRouterData, - _connectors: &settings::Connectors, + req: &RefundsRouterData, + _connectors: &Connectors, ) -> CustomResult { - let refund_amount = connector_utils::convert_amount( + let refund_amount = convert_amount( self.amount_converter, req.request.minor_refund_amount, req.request.currency, @@ -457,11 +469,11 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) .url(&types::RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() .set_body(types::RefundExecuteType::get_request_body( @@ -473,15 +485,15 @@ impl ConnectorIntegration, + data: &RefundsRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult, errors::ConnectorError> { let response: paybox::TransactionResponse = paybox::parse_url_encoded_to_struct(res.response)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -497,12 +509,12 @@ impl ConnectorIntegration for Paybox { +impl ConnectorIntegration for Paybox { fn get_headers( &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -512,16 +524,16 @@ impl ConnectorIntegration CustomResult { Ok(self.base_url(connectors).to_string()) } fn get_request_body( &self, - req: &types::RefundSyncRouterData, - _connectors: &settings::Connectors, + req: &RefundSyncRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_req = paybox::PayboxRsyncRequest::try_from(req)?; Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) @@ -529,12 +541,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() .set_body(types::RefundSyncType::get_request_body( @@ -546,15 +558,15 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: paybox::PayboxSyncResponse = paybox::parse_url_encoded_to_struct(res.response)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -571,41 +583,37 @@ impl ConnectorIntegration, - ) -> CustomResult { + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_event_type( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_resource_object( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } -impl - ConnectorIntegration< - api::CompleteAuthorize, - types::CompleteAuthorizeData, - types::PaymentsResponseData, - > for Paybox +impl ConnectorIntegration + for Paybox { fn get_headers( &self, - req: &types::PaymentsCompleteAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } fn get_content_type(&self) -> &'static str { @@ -613,17 +621,17 @@ impl } fn get_url( &self, - _req: &types::PaymentsCompleteAuthorizeRouterData, - connectors: &settings::Connectors, + _req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(self.base_url(connectors).to_string()) } fn get_request_body( &self, - req: &types::PaymentsCompleteAuthorizeRouterData, - _connectors: &settings::Connectors, + req: &PaymentsCompleteAuthorizeRouterData, + _connectors: &Connectors, ) -> CustomResult { - let amount = utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_amount, req.request.currency, @@ -635,12 +643,12 @@ impl } fn build_request( &self, - req: &types::PaymentsCompleteAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsCompleteAuthorizeType::get_url( self, req, connectors, )?) @@ -655,15 +663,15 @@ impl } fn handle_response( &self, - data: &types::PaymentsCompleteAuthorizeRouterData, + data: &PaymentsCompleteAuthorizeRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: paybox::TransactionResponse = paybox::parse_url_encoded_to_struct(res.response)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -678,19 +686,21 @@ impl self.build_error_response(res, event_builder) } } -impl services::ConnectorRedirectResponse for Paybox { +impl ConnectorRedirectResponse for Paybox { fn get_flow_type( &self, _query_params: &str, _json_payload: Option, - action: services::PaymentAction, - ) -> CustomResult { + action: PaymentAction, + ) -> CustomResult { match action { - services::PaymentAction::PSync - | services::PaymentAction::CompleteAuthorize - | services::PaymentAction::PaymentAuthenticateCompleteAuthorize => { - Ok(payments::CallConnectorAction::Trigger) + PaymentAction::PSync + | PaymentAction::CompleteAuthorize + | PaymentAction::PaymentAuthenticateCompleteAuthorize => { + Ok(CallConnectorAction::Trigger) } } } } + +impl ConnectorSpecifications for Paybox {} diff --git a/crates/router/src/connector/paybox/transformers.rs b/crates/hyperswitch_connectors/src/connectors/paybox/transformers.rs similarity index 67% rename from crates/router/src/connector/paybox/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/paybox/transformers.rs index d02f75693c48..effe6df28db2 100644 --- a/crates/router/src/connector/paybox/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/paybox/transformers.rs @@ -1,24 +1,34 @@ +use api_models::payments::AdditionalPaymentData; use bytes::Bytes; +use common_enums::enums; use common_utils::{ date_time::DateFormat, errors::CustomResult, ext_traits::ValueExt, types::MinorUnit, }; use error_stack::ResultExt; -use hyperswitch_connectors::utils::{AddressDetailsData, CardData}; use hyperswitch_domain_models::{ - router_data::ConnectorAuthType, router_response_types::RedirectForm, + payment_method_data::PaymentMethodData, + router_data::{ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::{CompleteAuthorizeData, PaymentsAuthorizeData, ResponseId}, + router_response_types::{ + MandateReference, PaymentsResponseData, RedirectForm, RefundsResponseData, + }, + types, +}; +use hyperswitch_interfaces::{ + consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE}, + errors, }; -use hyperswitch_interfaces::consts; -use masking::{PeekInterface, Secret}; +use masking::{ExposeInterface, PeekInterface, Secret}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use crate::{ - connector::utils::{ - self, PaymentsAuthorizeRequestData, PaymentsCompleteAuthorizeRequestData, RouterData, + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::{ + self, AddressDetailsData, CardData as _, PaymentsAuthorizeRequestData, + PaymentsCompleteAuthorizeRequestData, RouterData as _, }, - core::errors, - types::{self, api, domain, storage::enums}, }; - pub struct PayboxRouterData { pub amount: MinorUnit, pub router_data: T, @@ -42,6 +52,10 @@ const SUCCESS_CODE: &str = "00000"; const VERSION_PAYBOX: &str = "00104"; const PAY_ORIGIN_INTERNET: &str = "024"; const THREE_DS_FAIL_CODE: &str = "00000000"; +const RECURRING_ORIGIN: &str = "027"; +const MANDATE_REQUEST: &str = "00056"; +const MANDATE_AUTH_ONLY: &str = "00051"; +const MANDATE_AUTH_AND_CAPTURE_ONLY: &str = "00053"; type Error = error_stack::Report; @@ -50,6 +64,13 @@ type Error = error_stack::Report; pub enum PayboxPaymentsRequest { Card(PaymentsRequest), CardThreeDs(ThreeDSPaymentsRequest), + Mandate(MandatePaymentRequest), +} + +#[derive(Debug, Serialize)] +pub struct CardMandateInfo { + pub card_exp_month: Secret, + pub card_exp_year: Secret, } #[derive(Debug, Serialize)] @@ -99,6 +120,10 @@ pub struct PaymentsRequest { #[serde(rename = "ID3D")] #[serde(skip_serializing_if = "Option::is_none")] pub three_ds_data: Option>, + + #[serde(rename = "REFABONNE")] + #[serde(skip_serializing_if = "Option::is_none")] + pub customer_id: Option>, } #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] @@ -173,8 +198,7 @@ impl TryFrom<&PayboxRouterData<&types::PaymentsCaptureRouterData>> for PayboxCap let auth_data: PayboxAuthType = PayboxAuthType::try_from(&item.router_data.connector_auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; - let currency = diesel_models::enums::Currency::iso_4217(&item.router_data.request.currency) - .to_string(); + let currency = enums::Currency::iso_4217(item.router_data.request.currency).to_string(); let paybox_meta_data: PayboxMeta = utils::to_connector_meta(item.router_data.request.connector_meta.clone())?; let format_time = common_utils::date_time::format_date( @@ -363,15 +387,16 @@ impl TryFrom<&PayboxRouterData<&types::PaymentsAuthorizeRouterData>> for PayboxP item: &PayboxRouterData<&types::PaymentsAuthorizeRouterData>, ) -> Result { match item.router_data.request.payment_method_data.clone() { - domain::PaymentMethodData::Card(req_card) => { + PaymentMethodData::Card(req_card) => { let auth_data: PayboxAuthType = PayboxAuthType::try_from(&item.router_data.connector_auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; - let transaction_type = - get_transaction_type(item.router_data.request.capture_method)?; + let transaction_type = get_transaction_type( + item.router_data.request.capture_method, + item.router_data.request.is_mandate_payment(), + )?; let currency = - diesel_models::enums::Currency::iso_4217(&item.router_data.request.currency) - .to_string(); + enums::Currency::iso_4217(item.router_data.request.currency).to_string(); let expiration_date = req_card.get_card_expiry_month_year_2_digit_with_delimiter("".to_owned())?; let format_time = common_utils::date_time::format_date( @@ -420,18 +445,85 @@ impl TryFrom<&PayboxRouterData<&types::PaymentsAuthorizeRouterData>> for PayboxP rank: auth_data.rang, key: auth_data.cle, three_ds_data: None, + customer_id: match item.router_data.request.is_mandate_payment() { + true => { + let reference_id = item + .router_data + .connector_mandate_request_reference_id + .clone() + .ok_or_else(|| { + errors::ConnectorError::MissingRequiredField { + field_name: "connector_mandate_request_reference_id", + } + })?; + Some(Secret::new(reference_id)) + } + false => None, + }, })) } } + PaymentMethodData::MandatePayment => { + let mandate_data = extract_card_mandate_info( + item.router_data + .request + .additional_payment_method_data + .clone(), + )?; + Ok(Self::Mandate(MandatePaymentRequest::try_from(( + item, + mandate_data, + ))?)) + } _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), } } } -fn get_transaction_type(capture_method: Option) -> Result { - match capture_method { - Some(enums::CaptureMethod::Automatic) | None => Ok(AUTH_AND_CAPTURE_REQUEST.to_string()), - Some(enums::CaptureMethod::Manual) => Ok(AUTH_REQUEST.to_string()), +fn extract_card_mandate_info( + additional_payment_method_data: Option, +) -> Result { + match additional_payment_method_data { + Some(AdditionalPaymentData::Card(card_data)) => Ok(CardMandateInfo { + card_exp_month: card_data.card_exp_month.clone().ok_or_else(|| { + errors::ConnectorError::MissingRequiredField { + field_name: "card_exp_month", + } + })?, + card_exp_year: card_data.card_exp_year.clone().ok_or_else(|| { + errors::ConnectorError::MissingRequiredField { + field_name: "card_exp_year", + } + })?, + }), + _ => Err(errors::ConnectorError::MissingRequiredFields { + field_names: vec!["card_exp_month", "card_exp_year"], + } + .into()), + } +} + +fn get_transaction_type( + capture_method: Option, + is_mandate_request: bool, +) -> Result { + match (capture_method, is_mandate_request) { + (Some(enums::CaptureMethod::Automatic), false) + | (None, false) + | (Some(enums::CaptureMethod::SequentialAutomatic), false) => { + Ok(AUTH_AND_CAPTURE_REQUEST.to_string()) + } + (Some(enums::CaptureMethod::Automatic), true) | (None, true) => { + Err(errors::ConnectorError::NotSupported { + message: "Automatic Capture in CIT payments".to_string(), + connector: "Paybox", + })? + } + (Some(enums::CaptureMethod::Manual), false) => Ok(AUTH_REQUEST.to_string()), + (Some(enums::CaptureMethod::Manual), true) + | (Some(enums::CaptureMethod::SequentialAutomatic), true) => { + Ok(MANDATE_REQUEST.to_string()) + } _ => Err(errors::ConnectorError::CaptureMethodNotSupported)?, } } @@ -499,6 +591,11 @@ pub struct TransactionResponse { #[serde(rename = "COMMENTAIRE")] pub response_message: String, + #[serde(rename = "PORTEUR")] + pub carrier_id: Option>, + + #[serde(rename = "REFABONNE")] + pub customer_id: Option>, } pub fn parse_url_encoded_to_struct( @@ -580,25 +677,22 @@ pub struct PayboxCaptureResponse { pub response_message: String, } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { let response = item.response.clone(); let status = get_status_of_request(response.response_code.clone()); match status { true => Ok(Self { status: enums::AttemptStatus::Charged, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - response.paybox_order_id, - ), - redirection_data: None, - mandate_reference: None, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(response.paybox_order_id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: Some(serde_json::json!(PayboxMeta { connector_request_id: response.transaction_number.clone() })), @@ -611,7 +705,7 @@ impl ..item.data }), false => Ok(Self { - response: Err(types::ErrorResponse { + response: Err(ErrorResponse { code: response.response_code.clone(), message: response.response_message.clone(), reason: Some(response.response_message), @@ -625,40 +719,39 @@ impl } } -impl - TryFrom< - types::ResponseRouterData< - F, - PayboxResponse, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, - >, - > for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< - F, - PayboxResponse, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, - >, + item: ResponseRouterData, ) -> Result { match item.response.clone() { PayboxResponse::NonThreeDs(response) => { - let status = get_status_of_request(response.response_code.clone()); + let status: bool = get_status_of_request(response.response_code.clone()); match status { true => Ok(Self { - status: match item.data.request.is_auto_capture()? { - true => enums::AttemptStatus::Charged, - false => enums::AttemptStatus::Authorized, + status: match ( + item.data.request.is_auto_capture()?, + item.data.request.is_cit_mandate_payment(), + ) { + (_, true) | (false, false) => enums::AttemptStatus::Authorized, + (true, false) => enums::AttemptStatus::Charged, }, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( response.paybox_order_id, ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(response.carrier_id.as_ref().map( + |pm: &Secret| MandateReference { + connector_mandate_id: Some(pm.clone().expose()), + payment_method_id: None, + mandate_metadata: None, + connector_mandate_request_reference_id: + response.customer_id.map(|secret| secret.expose()), + }, + )), connector_metadata: Some(serde_json::json!(PayboxMeta { connector_request_id: response.transaction_number.clone() })), @@ -670,7 +763,7 @@ impl ..item.data }), false => Ok(Self { - response: Err(types::ErrorResponse { + response: Err(ErrorResponse { code: response.response_code.clone(), message: response.response_message.clone(), reason: Some(response.response_message), @@ -684,12 +777,12 @@ impl } PayboxResponse::ThreeDs(data) => Ok(Self { status: enums::AttemptStatus::AuthenticationPending, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::NoResponseId, - redirection_data: Some(RedirectForm::Html { + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::NoResponseId, + redirection_data: Box::new(Some(RedirectForm::Html { html_data: data.peek().to_string(), - }), - mandate_reference: None, + })), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -699,10 +792,10 @@ impl ..item.data }), PayboxResponse::Error(_) => Ok(Self { - response: Err(types::ErrorResponse { - code: consts::NO_ERROR_CODE.to_string(), - message: consts::NO_ERROR_MESSAGE.to_string(), - reason: Some(consts::NO_ERROR_MESSAGE.to_string()), + response: Err(ErrorResponse { + code: NO_ERROR_CODE.to_string(), + message: NO_ERROR_MESSAGE.to_string(), + reason: Some(NO_ERROR_MESSAGE.to_string()), status_code: item.http_code, attempt_status: None, connector_transaction_id: None, @@ -713,12 +806,12 @@ impl } } -impl TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { let response = item.response.clone(); let status = get_status_of_request(response.response_code.clone()); @@ -727,12 +820,10 @@ impl TryFrom Ok(Self { status: enums::AttemptStatus::from(connector_payment_status), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - response.paybox_order_id, - ), - redirection_data: None, - mandate_reference: None, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(response.paybox_order_id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: Some(serde_json::json!(PayboxMeta { connector_request_id: response.transaction_number.clone() })), @@ -744,7 +835,7 @@ impl TryFrom Ok(Self { - response: Err(types::ErrorResponse { + response: Err(ErrorResponse { code: response.response_code.clone(), message: response.response_message.clone(), reason: Some(response.response_message), @@ -791,8 +882,7 @@ impl TryFrom<&PayboxRouterData<&types::RefundsRouterData>> for PayboxRefun let auth_data: PayboxAuthType = PayboxAuthType::try_from(&item.router_data.connector_auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; - let currency = diesel_models::enums::Currency::iso_4217(&item.router_data.request.currency) - .to_string(); + let currency = enums::Currency::iso_4217(item.router_data.request.currency).to_string(); let format_time = common_utils::date_time::format_date( common_utils::date_time::now(), DateFormat::DDMMYYYYHHmmss, @@ -816,24 +906,24 @@ impl TryFrom<&PayboxRouterData<&types::RefundsRouterData>> for PayboxRefun } } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { let status = get_status_of_request(item.response.response_code.clone()); match status { true => Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item.response.transaction_number, refund_status: enums::RefundStatus::from(item.response.status), }), ..item.data }), false => Ok(Self { - response: Err(types::ErrorResponse { + response: Err(ErrorResponse { code: item.response.response_code.clone(), message: item.response.response_message.clone(), reason: Some(item.response.response_message), @@ -847,24 +937,24 @@ impl TryFrom> } } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { let status = get_status_of_request(item.response.response_code.clone()); match status { true => Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item.response.transaction_number, refund_status: common_enums::RefundStatus::Pending, }), ..item.data }), false => Ok(Self { - response: Err(types::ErrorResponse { + response: Err(ErrorResponse { code: item.response.response_code.clone(), message: item.response.response_message.clone(), reason: Some(item.response.response_message), @@ -886,38 +976,42 @@ pub struct PayboxErrorResponse { } impl - TryFrom< - types::ResponseRouterData< - F, - TransactionResponse, - types::CompleteAuthorizeData, - types::PaymentsResponseData, - >, - > for types::RouterData + TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< + item: ResponseRouterData< F, TransactionResponse, - types::CompleteAuthorizeData, - types::PaymentsResponseData, + CompleteAuthorizeData, + PaymentsResponseData, >, ) -> Result { let response = item.response.clone(); let status = get_status_of_request(response.response_code.clone()); match status { true => Ok(Self { - status: match item.data.request.is_auto_capture()? { - true => enums::AttemptStatus::Charged, - false => enums::AttemptStatus::Authorized, + status: match ( + item.data.request.is_auto_capture()?, + item.data.request.is_cit_mandate_payment(), + ) { + (_, true) | (false, false) => enums::AttemptStatus::Authorized, + (true, false) => enums::AttemptStatus::Charged, }, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - response.paybox_order_id, - ), - redirection_data: None, - mandate_reference: None, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(response.paybox_order_id), + redirection_data: Box::new(None), + mandate_reference: Box::new(response.carrier_id.as_ref().map(|pm| { + MandateReference { + connector_mandate_id: Some(pm.clone().expose()), + payment_method_id: None, + mandate_metadata: None, + connector_mandate_request_reference_id: response + .customer_id + .map(|secret| secret.expose()), + } + })), connector_metadata: Some(serde_json::json!(PayboxMeta { connector_request_id: response.transaction_number.clone() })), @@ -929,7 +1023,7 @@ impl ..item.data }), false => Ok(Self { - response: Err(types::ErrorResponse { + response: Err(ErrorResponse { code: response.response_code.clone(), message: response.response_message.clone(), reason: Some(response.response_message), @@ -969,15 +1063,16 @@ impl TryFrom<&PayboxRouterData<&types::PaymentsCompleteAuthorizeRouterData>> for .parse_value("RedirectionAuthResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; match item.router_data.request.payment_method_data.clone() { - Some(domain::PaymentMethodData::Card(req_card)) => { + Some(PaymentMethodData::Card(req_card)) => { let auth_data: PayboxAuthType = PayboxAuthType::try_from(&item.router_data.connector_auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; - let transaction_type = - get_transaction_type(item.router_data.request.capture_method)?; + let transaction_type = get_transaction_type( + item.router_data.request.capture_method, + item.router_data.request.is_mandate_payment(), + )?; let currency = - diesel_models::enums::Currency::iso_4217(&item.router_data.request.currency) - .to_string(); + enums::Currency::iso_4217(item.router_data.request.currency).to_string(); let expiration_date = req_card.get_card_expiry_month_year_2_digit_with_delimiter("".to_owned())?; let format_time = common_utils::date_time::format_date( @@ -1004,9 +1099,136 @@ impl TryFrom<&PayboxRouterData<&types::PaymentsCompleteAuthorizeRouterData>> for || Some(Secret::new(THREE_DS_FAIL_CODE.to_string())), |data| Some(data.clone()), ), + customer_id: match item.router_data.request.is_mandate_payment() { + true => { + let reference_id = item + .router_data + .connector_mandate_request_reference_id + .clone() + .ok_or_else(|| errors::ConnectorError::MissingRequiredField { + field_name: "connector_mandate_request_reference_id", + })?; + Some(Secret::new(reference_id)) + } + false => None, + }, }) } _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), } } } + +#[derive(Debug, Serialize)] +pub struct MandatePaymentRequest { + #[serde(rename = "DATEQ")] + pub date: String, + + #[serde(rename = "TYPE")] + pub transaction_type: String, + + #[serde(rename = "NUMQUESTION")] + pub paybox_request_number: String, + + #[serde(rename = "MONTANT")] + pub amount: MinorUnit, + + #[serde(rename = "REFERENCE")] + pub description_reference: String, + + #[serde(rename = "VERSION")] + pub version: String, + + #[serde(rename = "DEVISE")] + pub currency: String, + + #[serde(rename = "ACTIVITE")] + pub activity: String, + + #[serde(rename = "SITE")] + pub site: Secret, + + #[serde(rename = "RANG")] + pub rank: Secret, + + #[serde(rename = "CLE")] + pub key: Secret, + + #[serde(rename = "DATEVAL")] + pub cc_exp_date: Secret, + + #[serde(rename = "REFABONNE")] + pub customer_id: Secret, + + #[serde(rename = "PORTEUR")] + pub carrier_id: Secret, +} + +impl + TryFrom<( + &PayboxRouterData<&types::PaymentsAuthorizeRouterData>, + CardMandateInfo, + )> for MandatePaymentRequest +{ + type Error = error_stack::Report; + fn try_from( + (item, card_mandate_info): ( + &PayboxRouterData<&types::PaymentsAuthorizeRouterData>, + CardMandateInfo, + ), + ) -> Result { + let auth_data: PayboxAuthType = + PayboxAuthType::try_from(&item.router_data.connector_auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + let transaction_type = match item.router_data.request.capture_method { + Some(enums::CaptureMethod::Automatic) | None => { + Ok(MANDATE_AUTH_AND_CAPTURE_ONLY.to_string()) + } + Some(enums::CaptureMethod::Manual) => Ok(MANDATE_AUTH_ONLY.to_string()), + _ => Err(errors::ConnectorError::CaptureMethodNotSupported), + }?; + let currency = enums::Currency::iso_4217(item.router_data.request.currency).to_string(); + let format_time = common_utils::date_time::format_date( + common_utils::date_time::now(), + DateFormat::DDMMYYYYHHmmss, + ) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + Ok(Self { + date: format_time.clone(), + transaction_type, + paybox_request_number: get_paybox_request_number()?, + amount: item.router_data.request.minor_amount, + description_reference: item.router_data.connector_request_reference_id.clone(), + version: VERSION_PAYBOX.to_string(), + currency, + activity: RECURRING_ORIGIN.to_string(), + site: auth_data.site, + rank: auth_data.rang, + key: auth_data.cle, + customer_id: Secret::new( + item.router_data + .request + .get_connector_mandate_request_reference_id()?, + ), + carrier_id: Secret::new(item.router_data.request.get_connector_mandate_id()?), + cc_exp_date: get_card_expiry_month_year_2_digit( + card_mandate_info.card_exp_month.clone(), + card_mandate_info.card_exp_year.clone(), + )?, + }) + } +} + +fn get_card_expiry_month_year_2_digit( + card_exp_month: Secret, + card_exp_year: Secret, +) -> Result, errors::ConnectorError> { + Ok(Secret::new(format!( + "{}{}", + card_exp_month.peek(), + card_exp_year + .peek() + .get(card_exp_year.peek().len() - 2..) + .ok_or(errors::ConnectorError::RequestEncodingFailed)? + ))) +} diff --git a/crates/router/src/connector/payeezy.rs b/crates/hyperswitch_connectors/src/connectors/payeezy.rs similarity index 63% rename from crates/router/src/connector/payeezy.rs rename to crates/hyperswitch_connectors/src/connectors/payeezy.rs index 5783cf1bc14f..5621b9d1230e 100644 --- a/crates/router/src/connector/payeezy.rs +++ b/crates/hyperswitch_connectors/src/connectors/payeezy.rs @@ -1,34 +1,52 @@ -mod transformers; - -use std::fmt::Debug; +pub mod transformers; +use api_models::webhooks::IncomingWebhookEvent; use base64::Engine; -use common_utils::request::RequestContent; -use diesel_models::enums; +use common_enums::{CaptureMethod, PaymentMethod, PaymentMethodType}; +use common_utils::{ + errors::CustomResult, + ext_traits::ByteSliceExt, + request::{Method, Request, RequestBuilder, RequestContent}, +}; use error_stack::{report, ResultExt}; -use masking::ExposeInterface; +use hyperswitch_domain_models::{ + router_data::{AccessToken, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, + types::{ + PaymentsAuthorizeType, PaymentsCaptureType, PaymentsVoidType, RefundExecuteType, Response, + }, + webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, +}; +use masking::{ExposeInterface, Mask}; use rand::distributions::DistString; use ring::hmac; use transformers as payeezy; use crate::{ - configs::settings, - connector::utils as connector_utils, - consts, - core::errors::{self, CustomResult}, - events::connector_api_logs::ConnectorEvent, - headers, - services::{ - self, - request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, - }, - types::{ - self, - api::{self, ConnectorCommon, ConnectorCommonExt}, - ErrorResponse, Response, - }, - utils::BytesExt, + constants::headers, types::ResponseRouterData, utils::construct_not_implemented_error_report, }; #[derive(Debug, Clone)] @@ -40,9 +58,9 @@ where { fn build_headers( &self, - req: &types::RouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let auth = payeezy::PayeezyAuthType::try_from(&req.connector_auth_type)?; let request_payload = self .get_request_body(req, connectors)? @@ -66,7 +84,7 @@ where let key = hmac::Key::new(hmac::HMAC_SHA256, auth.api_secret.expose().as_bytes()); let tag = hmac::sign(&key, signature_string.expose().as_bytes()); let hmac_sign = hex::encode(tag); - let signature_value = consts::BASE64_ENGINE_URL_SAFE.encode(hmac_sign); + let signature_value = common_utils::consts::BASE64_ENGINE_URL_SAFE.encode(hmac_sign); Ok(vec![ ( headers::CONTENT_TYPE.to_string(), @@ -100,7 +118,7 @@ impl ConnectorCommon for Payeezy { "application/json" } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.payeezy.base_url.as_ref() } @@ -136,16 +154,19 @@ impl ConnectorCommon for Payeezy { } impl ConnectorValidation for Payeezy { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, - capture_method: Option, - _pmt: Option, + capture_method: Option, + _payment_method: PaymentMethod, + _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), - enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( - connector_utils::construct_not_implemented_error_report(capture_method, self.id()), + CaptureMethod::Automatic + | CaptureMethod::Manual + | CaptureMethod::SequentialAutomatic => Ok(()), + CaptureMethod::ManualMultiple | CaptureMethod::Scheduled => Err( + construct_not_implemented_error_report(capture_method, self.id()), ), } } @@ -154,22 +175,12 @@ impl ConnectorValidation for Payeezy { impl api::Payment for Payeezy {} impl api::MandateSetup for Payeezy {} -impl - ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Payeezy -{ +impl ConnectorIntegration for Payeezy { fn build_request( &self, - _req: &types::RouterData< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - >, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err( errors::ConnectorError::NotImplemented("Setup Mandate flow for Payeezy".to_string()) .into(), @@ -179,26 +190,20 @@ impl impl api::PaymentToken for Payeezy {} -impl - ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Payeezy +impl ConnectorIntegration + for Payeezy { // Not Implemented (R) } impl api::PaymentVoid for Payeezy {} -impl ConnectorIntegration - for Payeezy -{ +impl ConnectorIntegration for Payeezy { fn get_headers( &self, - req: &types::PaymentsCancelRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -208,8 +213,8 @@ impl ConnectorIntegration CustomResult { let connector_payment_id = req.request.connector_transaction_id.clone(); Ok(format!( @@ -221,8 +226,8 @@ impl ConnectorIntegration CustomResult { let connector_req = payeezy::PayeezyCaptureOrVoidRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -230,27 +235,25 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) - .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) - .set_body(types::PaymentsVoidType::get_request_body( - self, req, connectors, - )?) + RequestBuilder::new() + .method(Method::Post) + .url(&PaymentsVoidType::get_url(self, req, connectors)?) + .headers(PaymentsVoidType::get_headers(self, req, connectors)?) + .set_body(PaymentsVoidType::get_request_body(self, req, connectors)?) .build(), )) } fn handle_response( &self, - data: &types::PaymentsCancelRouterData, + data: &PaymentsCancelRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: payeezy::PayeezyPaymentsResponse = res .response .parse_struct("Payeezy PaymentsResponse") @@ -259,7 +262,7 @@ impl ConnectorIntegration - for Payeezy -{ -} +impl ConnectorIntegration for Payeezy {} impl api::PaymentSync for Payeezy {} -impl ConnectorIntegration - for Payeezy -{ +impl ConnectorIntegration for Payeezy { // default implementation of build_request method will be executed } impl api::PaymentCapture for Payeezy {} -impl ConnectorIntegration - for Payeezy -{ +impl ConnectorIntegration for Payeezy { fn get_headers( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -308,8 +304,8 @@ impl ConnectorIntegration CustomResult { let connector_payment_id = req.request.connector_transaction_id.clone(); Ok(format!( @@ -321,8 +317,8 @@ impl ConnectorIntegration CustomResult { let router_obj = payeezy::PayeezyRouterData::try_from(( &self.get_currency_unit(), @@ -337,17 +333,15 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) - .headers(types::PaymentsCaptureType::get_headers( - self, req, connectors, - )?) - .set_body(types::PaymentsCaptureType::get_request_body( + RequestBuilder::new() + .method(Method::Post) + .url(&PaymentsCaptureType::get_url(self, req, connectors)?) + .headers(PaymentsCaptureType::get_headers(self, req, connectors)?) + .set_body(PaymentsCaptureType::get_request_body( self, req, connectors, )?) .build(), @@ -356,10 +350,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: payeezy::PayeezyPaymentsResponse = res .response .parse_struct("Payeezy PaymentsResponse") @@ -368,7 +362,7 @@ impl ConnectorIntegration - for Payeezy -{ +impl ConnectorIntegration for Payeezy { //TODO: implement sessions flow } impl api::PaymentAuthorize for Payeezy {} -impl ConnectorIntegration - for Payeezy -{ +impl ConnectorIntegration for Payeezy { fn get_headers( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -412,16 +402,16 @@ impl ConnectorIntegration CustomResult { Ok(format!("{}v1/transactions", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::PaymentsAuthorizeRouterData, - _connectors: &settings::Connectors, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, ) -> CustomResult { let router_obj = payeezy::PayeezyRouterData::try_from(( &self.get_currency_unit(), @@ -436,19 +426,15 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsAuthorizeType::get_url( - self, req, connectors, - )?) - .headers(types::PaymentsAuthorizeType::get_headers( - self, req, connectors, - )?) - .set_body(types::PaymentsAuthorizeType::get_request_body( + RequestBuilder::new() + .method(Method::Post) + .url(&PaymentsAuthorizeType::get_url(self, req, connectors)?) + .headers(PaymentsAuthorizeType::get_headers(self, req, connectors)?) + .set_body(PaymentsAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -457,10 +443,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: payeezy::PayeezyPaymentsResponse = res .response .parse_struct("payeezy Response") @@ -469,7 +455,7 @@ impl ConnectorIntegration - for Payeezy -{ +impl ConnectorIntegration for Payeezy { fn get_headers( &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -507,8 +491,8 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, + req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { let connector_payment_id = req.request.connector_transaction_id.clone(); Ok(format!( @@ -520,8 +504,8 @@ impl ConnectorIntegration, - _connectors: &settings::Connectors, + req: &RefundsRouterData, + _connectors: &Connectors, ) -> CustomResult { let router_obj = payeezy::PayeezyRouterData::try_from(( &self.get_currency_unit(), @@ -535,28 +519,24 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::RefundExecuteType::get_url(self, req, connectors)?) - .headers(types::RefundExecuteType::get_headers( - self, req, connectors, - )?) - .set_body(types::RefundExecuteType::get_request_body( - self, req, connectors, - )?) + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&RefundExecuteType::get_url(self, req, connectors)?) + .headers(RefundExecuteType::get_headers(self, req, connectors)?) + .set_body(RefundExecuteType::get_request_body(self, req, connectors)?) .build(); Ok(Some(request)) } fn handle_response( &self, - data: &types::RefundsRouterData, + data: &RefundsRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult, errors::ConnectorError> { // Parse the response into a payeezy::RefundResponse let response: payeezy::RefundResponse = res .response @@ -567,12 +547,12 @@ impl ConnectorIntegration for Payeezy { +impl ConnectorIntegration for Payeezy { // default implementation of build_request method will be executed } #[async_trait::async_trait] -impl api::IncomingWebhook for Payeezy { +impl IncomingWebhook for Payeezy { fn get_webhook_object_reference_id( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_event_type( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { - Ok(api::IncomingWebhookEvent::EventNotSupported) + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Ok(IncomingWebhookEvent::EventNotSupported) } fn get_webhook_resource_object( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for Payeezy {} diff --git a/crates/router/src/connector/payeezy/transformers.rs b/crates/hyperswitch_connectors/src/connectors/payeezy/transformers.rs similarity index 57% rename from crates/router/src/connector/payeezy/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/payeezy/transformers.rs index 8c2ad2ef9799..49188803c4f6 100644 --- a/crates/router/src/connector/payeezy/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/payeezy/transformers.rs @@ -1,32 +1,43 @@ use cards::CardNumber; -use common_utils::ext_traits::Encode; +use common_enums::{enums, AttemptStatus, CaptureMethod, Currency, PaymentMethod}; +use common_utils::{errors::ParsingError, ext_traits::Encode}; use error_stack::ResultExt; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{ConnectorAuthType, RouterData}, + router_flow_types::Execute, + router_request_types::ResponseId, + router_response_types::{MandateReference, PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{api::CurrencyUnit, errors::ConnectorError}; use masking::{ExposeInterface, Secret}; use serde::{Deserialize, Serialize}; use crate::{ - connector::utils::{self, CardData, RouterData}, - core::errors, - types::{self, api, domain, storage::enums, transformers::ForeignFrom}, + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::{ + get_amount_as_string, get_unimplemented_payment_method_error_message, to_connector_meta, + CardData, CardIssuer, RouterData as _, + }, }; + #[derive(Debug, Serialize)] pub struct PayeezyRouterData { pub amount: String, pub router_data: T, } -impl TryFrom<(&api::CurrencyUnit, enums::Currency, i64, T)> for PayeezyRouterData { - type Error = error_stack::Report; +impl TryFrom<(&CurrencyUnit, Currency, i64, T)> for PayeezyRouterData { + type Error = error_stack::Report; fn try_from( - (currency_unit, currency, amount, router_data): ( - &api::CurrencyUnit, - enums::Currency, - i64, - T, - ), + (currency_unit, currency, amount, router_data): (&CurrencyUnit, Currency, i64, T), ) -> Result { - let amount = utils::get_amount_as_string(currency_unit, amount, currency)?; + let amount = get_amount_as_string(currency_unit, amount, currency)?; Ok(Self { amount, router_data, @@ -53,20 +64,20 @@ pub enum PayeezyCardType { Discover, } -impl TryFrom for PayeezyCardType { - type Error = error_stack::Report; - fn try_from(issuer: utils::CardIssuer) -> Result { +impl TryFrom for PayeezyCardType { + type Error = error_stack::Report; + fn try_from(issuer: CardIssuer) -> Result { match issuer { - utils::CardIssuer::AmericanExpress => Ok(Self::AmericanExpress), - utils::CardIssuer::Master => Ok(Self::Mastercard), - utils::CardIssuer::Discover => Ok(Self::Discover), - utils::CardIssuer::Visa => Ok(Self::Visa), - - utils::CardIssuer::Maestro - | utils::CardIssuer::DinersClub - | utils::CardIssuer::JCB - | utils::CardIssuer::CarteBlanche => Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("Payeezy"), + CardIssuer::AmericanExpress => Ok(Self::AmericanExpress), + CardIssuer::Master => Ok(Self::Mastercard), + CardIssuer::Discover => Ok(Self::Discover), + CardIssuer::Visa => Ok(Self::Visa), + + CardIssuer::Maestro + | CardIssuer::DinersClub + | CardIssuer::JCB + | CardIssuer::CarteBlanche => Err(ConnectorError::NotImplemented( + get_unimplemented_payment_method_error_message("Payeezy"), ))?, } } @@ -118,36 +129,37 @@ pub enum Initiator { CardHolder, } -impl TryFrom<&PayeezyRouterData<&types::PaymentsAuthorizeRouterData>> for PayeezyPaymentsRequest { - type Error = error_stack::Report; +impl TryFrom<&PayeezyRouterData<&PaymentsAuthorizeRouterData>> for PayeezyPaymentsRequest { + type Error = error_stack::Report; fn try_from( - item: &PayeezyRouterData<&types::PaymentsAuthorizeRouterData>, + item: &PayeezyRouterData<&PaymentsAuthorizeRouterData>, ) -> Result { match item.router_data.payment_method { - diesel_models::enums::PaymentMethod::Card => get_card_specific_payment_data(item), - - diesel_models::enums::PaymentMethod::CardRedirect - | diesel_models::enums::PaymentMethod::PayLater - | diesel_models::enums::PaymentMethod::Wallet - | diesel_models::enums::PaymentMethod::BankRedirect - | diesel_models::enums::PaymentMethod::BankTransfer - | diesel_models::enums::PaymentMethod::Crypto - | diesel_models::enums::PaymentMethod::BankDebit - | diesel_models::enums::PaymentMethod::Reward - | diesel_models::enums::PaymentMethod::RealTimePayment - | diesel_models::enums::PaymentMethod::Upi - | diesel_models::enums::PaymentMethod::Voucher - | diesel_models::enums::PaymentMethod::OpenBanking - | diesel_models::enums::PaymentMethod::GiftCard => { - Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()) + PaymentMethod::Card => get_card_specific_payment_data(item), + + PaymentMethod::CardRedirect + | PaymentMethod::PayLater + | PaymentMethod::Wallet + | PaymentMethod::BankRedirect + | PaymentMethod::BankTransfer + | PaymentMethod::Crypto + | PaymentMethod::BankDebit + | PaymentMethod::Reward + | PaymentMethod::RealTimePayment + | PaymentMethod::MobilePayment + | PaymentMethod::Upi + | PaymentMethod::Voucher + | PaymentMethod::OpenBanking + | PaymentMethod::GiftCard => { + Err(ConnectorError::NotImplemented("Payment methods".to_string()).into()) } } } } fn get_card_specific_payment_data( - item: &PayeezyRouterData<&types::PaymentsAuthorizeRouterData>, -) -> Result> { + item: &PayeezyRouterData<&PaymentsAuthorizeRouterData>, +) -> Result> { let merchant_ref = item.router_data.attempt_id.to_string(); let method = PayeezyPaymentMethodType::CreditCard; let amount = item.amount.clone(); @@ -167,16 +179,14 @@ fn get_card_specific_payment_data( }) } fn get_transaction_type_and_stored_creds( - item: &types::PaymentsAuthorizeRouterData, -) -> Result< - (PayeezyTransactionType, Option), - error_stack::Report, -> { + item: &PaymentsAuthorizeRouterData, +) -> Result<(PayeezyTransactionType, Option), error_stack::Report> +{ let connector_mandate_id = item.request.mandate_id.as_ref().and_then(|mandate_ids| { match mandate_ids.mandate_reference_id.clone() { Some(api_models::payments::MandateReferenceId::ConnectorMandateId( connector_mandate_ids, - )) => connector_mandate_ids.connector_mandate_id, + )) => connector_mandate_ids.get_connector_mandate_id(), _ => None, } }); @@ -203,36 +213,34 @@ fn get_transaction_type_and_stored_creds( ) } else { match item.request.capture_method { - Some(diesel_models::enums::CaptureMethod::Manual) => { - Ok((PayeezyTransactionType::Authorize, None)) - } - Some(diesel_models::enums::CaptureMethod::Automatic) => { + Some(CaptureMethod::Manual) => Ok((PayeezyTransactionType::Authorize, None)), + Some(CaptureMethod::SequentialAutomatic) | Some(CaptureMethod::Automatic) => { Ok((PayeezyTransactionType::Purchase, None)) } - Some(diesel_models::enums::CaptureMethod::ManualMultiple) - | Some(diesel_models::enums::CaptureMethod::Scheduled) - | None => Err(errors::ConnectorError::FlowNotSupported { - flow: item.request.capture_method.unwrap_or_default().to_string(), - connector: "Payeezy".to_string(), - }), + Some(CaptureMethod::ManualMultiple) | Some(CaptureMethod::Scheduled) | None => { + Err(ConnectorError::FlowNotSupported { + flow: item.request.capture_method.unwrap_or_default().to_string(), + connector: "Payeezy".to_string(), + }) + } }? }; Ok((transaction_type, stored_credentials)) } fn is_mandate_payment( - item: &types::PaymentsAuthorizeRouterData, + item: &PaymentsAuthorizeRouterData, connector_mandate_id: Option<&String>, ) -> bool { item.request.setup_mandate_details.is_some() || connector_mandate_id.is_some() } fn get_payment_method_data( - item: &PayeezyRouterData<&types::PaymentsAuthorizeRouterData>, -) -> Result> { + item: &PayeezyRouterData<&PaymentsAuthorizeRouterData>, +) -> Result> { match item.router_data.request.payment_method_data { - domain::PaymentMethodData::Card(ref card) => { + PaymentMethodData::Card(ref card) => { let card_type = PayeezyCardType::try_from(card.get_card_issuer()?)?; let payeezy_card = PayeezyCard { card_type, @@ -247,24 +255,26 @@ fn get_payment_method_data( Ok(PayeezyPaymentMethod::PayeezyCard(payeezy_card)) } - 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::RealTimePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { - Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("Payeezy"), + PaymentMethodData::CardRedirect(_) + | PaymentMethodData::Wallet(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + Err(ConnectorError::NotImplemented( + get_unimplemented_payment_method_error_message("Payeezy"), ))? } } @@ -277,10 +287,10 @@ pub struct PayeezyAuthType { pub(super) merchant_token: Secret, } -impl TryFrom<&types::ConnectorAuthType> for PayeezyAuthType { - type Error = error_stack::Report; - fn try_from(item: &types::ConnectorAuthType) -> Result { - if let types::ConnectorAuthType::SignatureKey { +impl TryFrom<&ConnectorAuthType> for PayeezyAuthType { + type Error = error_stack::Report; + fn try_from(item: &ConnectorAuthType) -> Result { + if let ConnectorAuthType::SignatureKey { api_key, key1, api_secret, @@ -292,7 +302,7 @@ impl TryFrom<&types::ConnectorAuthType> for PayeezyAuthType { merchant_token: key1.to_owned(), }) } else { - Err(errors::ConnectorError::FailedToObtainAuthType.into()) + Err(ConnectorError::FailedToObtainAuthType.into()) } } } @@ -340,16 +350,12 @@ pub struct PayeezyCaptureOrVoidRequest { currency_code: String, } -impl TryFrom<&PayeezyRouterData<&types::PaymentsCaptureRouterData>> - for PayeezyCaptureOrVoidRequest -{ - type Error = error_stack::Report; - fn try_from( - item: &PayeezyRouterData<&types::PaymentsCaptureRouterData>, - ) -> Result { +impl TryFrom<&PayeezyRouterData<&PaymentsCaptureRouterData>> for PayeezyCaptureOrVoidRequest { + type Error = error_stack::Report; + fn try_from(item: &PayeezyRouterData<&PaymentsCaptureRouterData>) -> Result { let metadata: PayeezyPaymentsMetadata = - utils::to_connector_meta(item.router_data.request.connector_meta.clone()) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; + to_connector_meta(item.router_data.request.connector_meta.clone()) + .change_context(ConnectorError::RequestEncodingFailed)?; Ok(Self { transaction_type: PayeezyTransactionType::Capture, amount: item.amount.clone(), @@ -359,18 +365,18 @@ impl TryFrom<&PayeezyRouterData<&types::PaymentsCaptureRouterData>> } } -impl TryFrom<&types::PaymentsCancelRouterData> for PayeezyCaptureOrVoidRequest { - type Error = error_stack::Report; - fn try_from(item: &types::PaymentsCancelRouterData) -> Result { +impl TryFrom<&PaymentsCancelRouterData> for PayeezyCaptureOrVoidRequest { + type Error = error_stack::Report; + fn try_from(item: &PaymentsCancelRouterData) -> Result { let metadata: PayeezyPaymentsMetadata = - utils::to_connector_meta(item.request.connector_meta.clone()) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; + to_connector_meta(item.request.connector_meta.clone()) + .change_context(ConnectorError::RequestEncodingFailed)?; Ok(Self { transaction_type: PayeezyTransactionType::Void, amount: item .request .amount - .ok_or(errors::ConnectorError::RequestEncodingFailed)? + .ok_or(ConnectorError::RequestEncodingFailed)? .to_string(), currency_code: item.request.currency.unwrap_or_default().to_string(), transaction_tag: metadata.transaction_tag, @@ -396,43 +402,43 @@ pub struct PayeezyPaymentsMetadata { transaction_tag: String, } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { - type Error = error_stack::Report; + type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { let metadata = item .response .transaction_tag .map(|txn_tag| construct_payeezy_payments_metadata(txn_tag).encode_to_value()) .transpose() - .change_context(errors::ConnectorError::ResponseHandlingFailed)?; + .change_context(ConnectorError::ResponseHandlingFailed)?; let mandate_reference = item .response .stored_credentials .map(|credentials| credentials.cardbrand_original_transaction_id) - .map(|id| types::MandateReference { + .map(|id| MandateReference { connector_mandate_id: Some(id.expose()), payment_method_id: None, mandate_metadata: None, + connector_mandate_request_reference_id: None, }); - let status = enums::AttemptStatus::foreign_from(( + let status = get_status( item.response.transaction_status, item.response.transaction_type, - )); + ); Ok(Self { status, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( item.response.transaction_id.clone(), ), - redirection_data: None, - mandate_reference, + redirection_data: Box::new(None), + mandate_reference: Box::new(mandate_reference), connector_metadata: metadata, network_txn_id: None, connector_response_reference_id: Some( @@ -448,26 +454,28 @@ impl } } -impl ForeignFrom<(PayeezyPaymentStatus, PayeezyTransactionType)> for enums::AttemptStatus { - fn foreign_from((status, method): (PayeezyPaymentStatus, PayeezyTransactionType)) -> Self { - match status { - PayeezyPaymentStatus::Approved => match method { - PayeezyTransactionType::Authorize => Self::Authorized, - PayeezyTransactionType::Capture - | PayeezyTransactionType::Purchase - | PayeezyTransactionType::Recurring => Self::Charged, - PayeezyTransactionType::Void => Self::Voided, - PayeezyTransactionType::Refund | PayeezyTransactionType::Pending => Self::Pending, - }, - PayeezyPaymentStatus::Declined | PayeezyPaymentStatus::NotProcessed => match method { - PayeezyTransactionType::Capture => Self::CaptureFailed, - PayeezyTransactionType::Authorize - | PayeezyTransactionType::Purchase - | PayeezyTransactionType::Recurring => Self::AuthorizationFailed, - PayeezyTransactionType::Void => Self::VoidFailed, - PayeezyTransactionType::Refund | PayeezyTransactionType::Pending => Self::Pending, - }, - } +fn get_status(status: PayeezyPaymentStatus, method: PayeezyTransactionType) -> AttemptStatus { + match status { + PayeezyPaymentStatus::Approved => match method { + PayeezyTransactionType::Authorize => AttemptStatus::Authorized, + PayeezyTransactionType::Capture + | PayeezyTransactionType::Purchase + | PayeezyTransactionType::Recurring => AttemptStatus::Charged, + PayeezyTransactionType::Void => AttemptStatus::Voided, + PayeezyTransactionType::Refund | PayeezyTransactionType::Pending => { + AttemptStatus::Pending + } + }, + PayeezyPaymentStatus::Declined | PayeezyPaymentStatus::NotProcessed => match method { + PayeezyTransactionType::Capture => AttemptStatus::CaptureFailed, + PayeezyTransactionType::Authorize + | PayeezyTransactionType::Purchase + | PayeezyTransactionType::Recurring => AttemptStatus::AuthorizationFailed, + PayeezyTransactionType::Void => AttemptStatus::VoidFailed, + PayeezyTransactionType::Refund | PayeezyTransactionType::Pending => { + AttemptStatus::Pending + } + }, } } @@ -481,14 +489,12 @@ pub struct PayeezyRefundRequest { currency_code: String, } -impl TryFrom<&PayeezyRouterData<&types::RefundsRouterData>> for PayeezyRefundRequest { - type Error = error_stack::Report; - fn try_from( - item: &PayeezyRouterData<&types::RefundsRouterData>, - ) -> Result { +impl TryFrom<&PayeezyRouterData<&RefundsRouterData>> for PayeezyRefundRequest { + type Error = error_stack::Report; + fn try_from(item: &PayeezyRouterData<&RefundsRouterData>) -> Result { let metadata: PayeezyPaymentsMetadata = - utils::to_connector_meta(item.router_data.request.connector_metadata.clone()) - .change_context(errors::ConnectorError::RequestEncodingFailed)?; + to_connector_meta(item.router_data.request.connector_metadata.clone()) + .change_context(ConnectorError::RequestEncodingFailed)?; Ok(Self { transaction_type: PayeezyTransactionType::Refund, amount: item.amount.clone(), @@ -537,15 +543,13 @@ pub struct RefundResponse { pub gateway_message: String, } -impl TryFrom> - for types::RefundsRouterData -{ - type Error = error_stack::Report; +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item.response.transaction_id, refund_status: enums::RefundStatus::from(item.response.transaction_status), }), diff --git a/crates/router/src/connector/payu.rs b/crates/hyperswitch_connectors/src/connectors/payu.rs similarity index 60% rename from crates/router/src/connector/payu.rs rename to crates/hyperswitch_connectors/src/connectors/payu.rs index 4ff317d1f713..5ceb759df8cc 100644 --- a/crates/router/src/connector/payu.rs +++ b/crates/hyperswitch_connectors/src/connectors/payu.rs @@ -1,34 +1,68 @@ pub mod transformers; -use std::fmt::Debug; - -use common_utils::request::RequestContent; -use diesel_models::enums; +use api_models::webhooks::IncomingWebhookEvent; +use common_enums::enums; +use common_utils::{ + errors::CustomResult, + ext_traits::ByteSliceExt, + request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, MinorUnit, MinorUnitForConnector}, +}; use error_stack::{report, ResultExt}; -use masking::PeekInterface; -use transformers as payu; - -use crate::{ - configs::settings, - connector::utils as connector_utils, - core::errors::{self, CustomResult}, - events::connector_api_logs::ConnectorEvent, - headers, - services::{ - self, - request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, +use hyperswitch_domain_models::{ + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, types::{ - self, - api::{self, ConnectorCommon, ConnectorCommonExt}, - ErrorResponse, + PaymentsAuthorizeType, PaymentsCaptureType, PaymentsSyncType, PaymentsVoidType, + RefreshTokenType, RefundExecuteType, RefundSyncType, Response, }, - utils::BytesExt, + webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, }; +use masking::{Mask, PeekInterface}; +use transformers as payu; + +use crate::{ + constants::headers, + types::{RefreshTokenRouterData, ResponseRouterData}, + utils, + utils::construct_not_supported_error_report, +}; + +#[derive(Clone)] +pub struct Payu { + amount_converter: &'static (dyn AmountConvertor + Sync), +} -#[derive(Debug, Clone)] -pub struct Payu; +impl Payu { + pub fn new() -> &'static Self { + &Self { + amount_converter: &MinorUnitForConnector, + } + } +} impl ConnectorCommonExt for Payu where @@ -36,9 +70,9 @@ where { fn build_headers( &self, - req: &types::RouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let mut headers = vec![( headers::CONTENT_TYPE.to_string(), self.get_content_type().to_string().into(), @@ -67,14 +101,14 @@ impl ConnectorCommon for Payu { "application/json" } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.payu.base_url.as_ref() } fn get_auth_header( &self, - auth_type: &types::ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { let auth = payu::PayuAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; Ok(vec![( @@ -85,7 +119,7 @@ impl ConnectorCommon for Payu { fn build_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { let response: payu::PayuErrorResponse = res @@ -108,16 +142,19 @@ impl ConnectorCommon for Payu { } impl ConnectorValidation for Payu { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( - connector_utils::construct_not_supported_error_report(capture_method, self.id()), + construct_not_supported_error_report(capture_method, self.id()), ), } } @@ -126,22 +163,12 @@ impl ConnectorValidation for Payu { impl api::Payment for Payu {} impl api::MandateSetup for Payu {} -impl - ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Payu -{ +impl ConnectorIntegration for Payu { fn build_request( &self, - _req: &types::RouterData< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - >, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err( errors::ConnectorError::NotImplemented("Setup Mandate flow for Payu".to_string()) .into(), @@ -151,26 +178,20 @@ impl impl api::PaymentToken for Payu {} -impl - ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Payu +impl ConnectorIntegration + for Payu { // Not Implemented (R) } impl api::PaymentVoid for Payu {} -impl ConnectorIntegration - for Payu -{ +impl ConnectorIntegration for Payu { fn get_headers( &self, - req: &types::PaymentsCancelRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -180,8 +201,8 @@ impl ConnectorIntegration CustomResult { let connector_payment_id = &req.request.connector_transaction_id; Ok(format!( @@ -193,23 +214,23 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Delete) - .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Delete) + .url(&PaymentsVoidType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) + .headers(PaymentsVoidType::get_headers(self, req, connectors)?) .build(); Ok(Some(request)) } fn handle_response( &self, - data: &types::PaymentsCancelRouterData, + data: &PaymentsCancelRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: payu::PayuPaymentsCancelResponse = res .response .parse_struct("PaymentCancelResponse") @@ -218,7 +239,7 @@ impl ConnectorIntegration, ) -> CustomResult { self.build_error_response(res, event_builder) @@ -236,13 +257,11 @@ impl ConnectorIntegration - for Payu -{ +impl ConnectorIntegration for Payu { fn get_url( &self, - _req: &types::RefreshTokenRouterData, - connectors: &settings::Connectors, + _req: &RefreshTokenRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!( "{}{}", @@ -257,21 +276,19 @@ impl ConnectorIntegration CustomResult)>, errors::ConnectorError> { + _req: &RefreshTokenRouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { Ok(vec![( headers::CONTENT_TYPE.to_string(), - types::RefreshTokenType::get_content_type(self) - .to_string() - .into(), + RefreshTokenType::get_content_type(self).to_string().into(), )]) } fn get_request_body( &self, - req: &types::RefreshTokenRouterData, - _connectors: &settings::Connectors, + req: &RefreshTokenRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_req = payu::PayuAuthUpdateRequest::try_from(req)?; @@ -280,18 +297,16 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &RefreshTokenRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { let req = Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .attach_default_headers() - .headers(types::RefreshTokenType::get_headers(self, req, connectors)?) - .url(&types::RefreshTokenType::get_url(self, req, connectors)?) - .set_body(types::RefreshTokenType::get_request_body( - self, req, connectors, - )?) + .headers(RefreshTokenType::get_headers(self, req, connectors)?) + .url(&RefreshTokenType::get_url(self, req, connectors)?) + .set_body(RefreshTokenType::get_request_body(self, req, connectors)?) .build(), ); @@ -299,10 +314,10 @@ impl ConnectorIntegration, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: payu::PayuAuthUpdateResponse = res .response .parse_struct("payu PayuAuthUpdateResponse") @@ -311,7 +326,7 @@ impl ConnectorIntegration, ) -> CustomResult { let response: payu::PayuAccessTokenErrorResponse = res @@ -344,14 +359,12 @@ impl ConnectorIntegration - for Payu -{ +impl ConnectorIntegration for Payu { fn get_headers( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -361,8 +374,8 @@ impl ConnectorIntegration CustomResult { let connector_payment_id = req .request @@ -379,32 +392,32 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) - .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Get) + .url(&PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .headers(PaymentsSyncType::get_headers(self, req, connectors)?) .build(), )) } fn handle_response( &self, - data: &types::PaymentsSyncRouterData, + data: &PaymentsSyncRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: payu::PayuPaymentsSyncResponse = res .response .parse_struct("payu OrderResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -414,7 +427,7 @@ impl ConnectorIntegration, ) -> CustomResult { self.build_error_response(res, event_builder) @@ -422,14 +435,12 @@ impl ConnectorIntegration - for Payu -{ +impl ConnectorIntegration for Payu { fn get_headers( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -439,8 +450,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}{}{}{}", @@ -453,8 +464,8 @@ impl ConnectorIntegration CustomResult { let connector_req = payu::PayuPaymentsCaptureRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -462,18 +473,16 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Put) - .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Put) + .url(&PaymentsCaptureType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::PaymentsCaptureType::get_headers( - self, req, connectors, - )?) - .set_body(types::PaymentsCaptureType::get_request_body( + .headers(PaymentsCaptureType::get_headers(self, req, connectors)?) + .set_body(PaymentsCaptureType::get_request_body( self, req, connectors, )?) .build(), @@ -482,17 +491,17 @@ impl ConnectorIntegration, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: payu::PayuPaymentsCaptureResponse = res .response .parse_struct("payu CaptureResponse") .change_context(errors::ConnectorError::RequestEncodingFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -502,7 +511,7 @@ impl ConnectorIntegration, ) -> CustomResult { self.build_error_response(res, event_builder) @@ -511,22 +520,18 @@ impl ConnectorIntegration - for Payu -{ +impl ConnectorIntegration for Payu { //TODO: implement sessions flow } impl api::PaymentAuthorize for Payu {} -impl ConnectorIntegration - for Payu -{ +impl ConnectorIntegration for Payu { fn get_headers( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -536,8 +541,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}{}", @@ -548,33 +553,33 @@ impl ConnectorIntegration CustomResult { - let connector_req = payu::PayuPaymentsRequest::try_from(req)?; + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + + let connector_router_data = payu::PayuRouterData::try_from((amount, req))?; + + let connector_req = payu::PayuPaymentsRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, - req: &types::RouterData< - api::Authorize, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, - >, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &RouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsAuthorizeType::get_url( - self, req, connectors, - )?) + RequestBuilder::new() + .method(Method::Post) + .url(&PaymentsAuthorizeType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::PaymentsAuthorizeType::get_headers( - self, req, connectors, - )?) - .set_body(types::PaymentsAuthorizeType::get_request_body( + .headers(PaymentsAuthorizeType::get_headers(self, req, connectors)?) + .set_body(PaymentsAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -583,17 +588,17 @@ impl ConnectorIntegration, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: payu::PayuPaymentsResponse = res .response .parse_struct("PayuPaymentsResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -603,7 +608,7 @@ impl ConnectorIntegration, ) -> CustomResult { self.build_error_response(res, event_builder) @@ -614,12 +619,12 @@ impl api::Refund for Payu {} impl api::RefundExecute for Payu {} impl api::RefundSync for Payu {} -impl ConnectorIntegration for Payu { +impl ConnectorIntegration for Payu { fn get_headers( &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -629,8 +634,8 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, + req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!( "{}{}{}{}", @@ -643,45 +648,48 @@ impl ConnectorIntegration, - _connectors: &settings::Connectors, + req: &RefundsRouterData, + _connectors: &Connectors, ) -> CustomResult { - let connector_req = payu::PayuRefundRequest::try_from(req)?; + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_refund_amount, + req.request.currency, + )?; + + let connector_router_data = payu::PayuRouterData::try_from((amount, req))?; + let connector_req = payu::PayuRefundRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::RefundExecuteType::get_headers( - self, req, connectors, - )?) - .set_body(types::RefundExecuteType::get_request_body( - self, req, connectors, - )?) + .headers(RefundExecuteType::get_headers(self, req, connectors)?) + .set_body(RefundExecuteType::get_request_body(self, req, connectors)?) .build(); Ok(Some(request)) } fn handle_response( &self, - data: &types::RefundsRouterData, + data: &RefundsRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult, errors::ConnectorError> { + res: Response, + ) -> CustomResult, errors::ConnectorError> { let response: payu::RefundResponse = res .response .parse_struct("payu RefundResponse") .change_context(errors::ConnectorError::RequestEncodingFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -691,19 +699,19 @@ impl ConnectorIntegration, ) -> CustomResult { self.build_error_response(res, event_builder) } } -impl ConnectorIntegration for Payu { +impl ConnectorIntegration for Payu { fn get_headers( &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -713,8 +721,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}{}{}{}", @@ -727,32 +735,32 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) - .url(&types::RefundSyncType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Get) + .url(&RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::RefundSyncType::get_headers(self, req, connectors)?) + .headers(RefundSyncType::get_headers(self, req, connectors)?) .build(), )) } fn handle_response( &self, - data: &types::RefundSyncRouterData, + data: &RefundSyncRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: payu::RefundSyncResponse = res.response .parse_struct("payu RefundResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -762,7 +770,7 @@ impl ConnectorIntegration, ) -> CustomResult { self.build_error_response(res, event_builder) @@ -770,25 +778,27 @@ impl ConnectorIntegration, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_event_type( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { - Ok(api::IncomingWebhookEvent::EventNotSupported) + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Ok(IncomingWebhookEvent::EventNotSupported) } fn get_webhook_resource_object( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for Payu {} diff --git a/crates/router/src/connector/payu/transformers.rs b/crates/hyperswitch_connectors/src/connectors/payu/transformers.rs similarity index 75% rename from crates/router/src/connector/payu/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/payu/transformers.rs index 5365fc74ef2e..9bdb52de4f0d 100644 --- a/crates/router/src/connector/payu/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/payu/transformers.rs @@ -1,28 +1,57 @@ use base64::Engine; -use common_utils::pii::{Email, IpAddress}; +use common_enums::enums; +use common_utils::{ + consts::BASE64_ENGINE, + pii::{Email, IpAddress}, + types::MinorUnit, +}; use error_stack::ResultExt; +use hyperswitch_domain_models::{ + payment_method_data::{PaymentMethodData, WalletData}, + router_data::{AccessToken, ConnectorAuthType, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types, +}; +use hyperswitch_interfaces::errors; +use masking::Secret; use serde::{Deserialize, Serialize}; use crate::{ - connector::utils::AccessTokenRequestInfo, - consts, - core::errors, - pii::Secret, - types::{self, api, domain, storage::enums}, + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::AccessTokenRequestInfo as _, }; const WALLET_IDENTIFIER: &str = "PBL"; -#[derive(Debug, Serialize, Eq, PartialEq)] +#[derive(Debug, Serialize)] +pub struct PayuRouterData { + pub amount: MinorUnit, + pub router_data: T, +} + +impl TryFrom<(MinorUnit, T)> for PayuRouterData { + type Error = error_stack::Report; + fn try_from((amount, item): (MinorUnit, T)) -> Result { + Ok(Self { + amount, + router_data: item, + }) + } +} + +#[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct PayuPaymentsRequest { customer_ip: Secret, merchant_pos_id: Secret, - total_amount: i64, + total_amount: MinorUnit, currency_code: enums::Currency, description: String, pay_methods: PayuPaymentMethod, continue_url: Option, + ext_order_id: Option, } #[derive(Debug, Eq, PartialEq, Serialize)] @@ -65,12 +94,14 @@ pub enum PayuWalletCode { Jp, } -impl TryFrom<&types::PaymentsAuthorizeRouterData> for PayuPaymentsRequest { +impl TryFrom<&PayuRouterData<&types::PaymentsAuthorizeRouterData>> for PayuPaymentsRequest { type Error = error_stack::Report; - fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result { - let auth_type = PayuAuthType::try_from(&item.connector_auth_type)?; - let payment_method = match item.request.payment_method_data.clone() { - domain::PaymentMethodData::Card(ccard) => Ok(PayuPaymentMethod { + fn try_from( + item: &PayuRouterData<&types::PaymentsAuthorizeRouterData>, + ) -> Result { + let auth_type = PayuAuthType::try_from(&item.router_data.connector_auth_type)?; + let payment_method = match item.router_data.request.payment_method_data.clone() { + PaymentMethodData::Card(ccard) => Ok(PayuPaymentMethod { pay_method: PayuPaymentMethodData::Card(PayuCard::Card { number: ccard.card_number, expiration_month: ccard.card_exp_month, @@ -78,19 +109,19 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PayuPaymentsRequest { cvv: ccard.card_cvc, }), }), - domain::PaymentMethodData::Wallet(wallet_data) => match wallet_data { - domain::WalletData::GooglePay(data) => Ok(PayuPaymentMethod { + PaymentMethodData::Wallet(wallet_data) => match wallet_data { + WalletData::GooglePay(data) => Ok(PayuPaymentMethod { pay_method: PayuPaymentMethodData::Wallet({ PayuWallet { value: PayuWalletCode::Ap, wallet_type: WALLET_IDENTIFIER.to_string(), authorization_code: Secret::new( - consts::BASE64_ENGINE.encode(data.tokenization_data.token), + BASE64_ENGINE.encode(data.tokenization_data.token), ), } }), }), - domain::WalletData::ApplePay(data) => Ok(PayuPaymentMethod { + WalletData::ApplePay(data) => Ok(PayuPaymentMethod { pay_method: PayuPaymentMethodData::Wallet({ PayuWallet { value: PayuWalletCode::Jp, @@ -107,7 +138,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PayuPaymentsRequest { "Unknown payment method".to_string(), )), }?; - let browser_info = item.request.browser_info.clone().ok_or( + let browser_info = item.router_data.request.browser_info.clone().ok_or( errors::ConnectorError::MissingRequiredField { field_name: "browser_info", }, @@ -122,9 +153,10 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PayuPaymentsRequest { .to_string(), ), merchant_pos_id: auth_type.merchant_pos_id, - total_amount: item.request.amount, - currency_code: item.request.currency, - description: item.description.clone().ok_or( + ext_order_id: Some(item.router_data.connector_request_reference_id.clone()), + total_amount: item.amount.to_owned(), + currency_code: item.router_data.request.currency, + description: item.router_data.description.clone().ok_or( errors::ConnectorError::MissingRequiredField { field_name: "item.description", }, @@ -140,11 +172,11 @@ pub struct PayuAuthType { pub(super) merchant_pos_id: Secret, } -impl TryFrom<&types::ConnectorAuthType> for PayuAuthType { +impl TryFrom<&ConnectorAuthType> for PayuAuthType { type Error = error_stack::Report; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { + fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - types::ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { + ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { api_key: api_key.to_owned(), merchant_pos_id: key1.to_owned(), }), @@ -188,22 +220,19 @@ pub struct PayuPaymentsResponse { pub ext_order_id: Option, } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { Ok(Self { status: enums::AttemptStatus::from(item.response.status.status_code), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - item.response.order_id.clone(), - ), - redirection_data: None, - mandate_reference: None, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.order_id.clone()), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: item @@ -241,26 +270,19 @@ pub struct PayuPaymentsCaptureResponse { status: PayuPaymentStatusData, } -impl - TryFrom< - types::ResponseRouterData, - > for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< - F, - PayuPaymentsCaptureResponse, - T, - types::PaymentsResponseData, - >, + item: ResponseRouterData, ) -> Result { Ok(Self { status: enums::AttemptStatus::from(item.response.status.status_code.clone()), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::NoResponseId, - redirection_data: None, - mandate_reference: None, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::NoResponseId, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -298,15 +320,15 @@ pub struct PayuAuthUpdateResponse { pub grant_type: String, } -impl TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { Ok(Self { - response: Ok(types::AccessToken { + response: Ok(AccessToken { token: item.response.access_token, expires: item.response.expires_in, }), @@ -323,28 +345,19 @@ pub struct PayuPaymentsCancelResponse { pub status: PayuPaymentStatusData, } -impl - TryFrom< - types::ResponseRouterData, - > for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< - F, - PayuPaymentsCancelResponse, - T, - types::PaymentsResponseData, - >, + item: ResponseRouterData, ) -> Result { Ok(Self { status: enums::AttemptStatus::from(item.response.status.status_code.clone()), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - item.response.order_id.clone(), - ), - redirection_data: None, - mandate_reference: None, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.order_id.clone()), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: item @@ -457,18 +470,12 @@ pub struct PayuPaymentsSyncResponse { properties: Option>, } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< - F, - PayuPaymentsSyncResponse, - T, - types::PaymentsResponseData, - >, + item: ResponseRouterData, ) -> Result { let order = match item.response.orders.first() { Some(order) => order, @@ -476,10 +483,10 @@ impl }; Ok(Self { status: enums::AttemptStatus::from(order.status.clone()), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(order.order_id.clone()), - redirection_data: None, - mandate_reference: None, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(order.order_id.clone()), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: order @@ -500,10 +507,10 @@ impl } } -#[derive(Default, Debug, Eq, PartialEq, Serialize)] +#[derive(Default, Debug, Serialize)] pub struct PayuRefundRequestData { description: String, - amount: Option, + amount: Option, } #[derive(Default, Debug, Serialize)] @@ -511,12 +518,12 @@ pub struct PayuRefundRequest { refund: PayuRefundRequestData, } -impl TryFrom<&types::RefundsRouterData> for PayuRefundRequest { +impl TryFrom<&PayuRouterData<&types::RefundsRouterData>> for PayuRefundRequest { type Error = error_stack::Report; - fn try_from(item: &types::RefundsRouterData) -> Result { + fn try_from(item: &PayuRouterData<&types::RefundsRouterData>) -> Result { Ok(Self { refund: PayuRefundRequestData { - description: item.request.reason.clone().ok_or( + description: item.router_data.request.reason.clone().ok_or( errors::ConnectorError::MissingRequiredField { field_name: "item.request.reason", }, @@ -569,16 +576,16 @@ pub struct RefundResponse { refund: PayuRefundResponseData, } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { let refund_status = enums::RefundStatus::from(item.response.refund.status); Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item.response.refund.refund_id, refund_status, }), @@ -591,19 +598,19 @@ impl TryFrom> pub struct RefundSyncResponse { refunds: Vec, } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { let refund = match item.response.refunds.first() { Some(refund) => refund, _ => Err(errors::ConnectorError::ResponseHandlingFailed)?, }; Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: refund.refund_id.clone(), refund_status: enums::RefundStatus::from(refund.status.clone()), }), diff --git a/crates/router/src/connector/placetopay.rs b/crates/hyperswitch_connectors/src/connectors/placetopay.rs similarity index 67% rename from crates/router/src/connector/placetopay.rs rename to crates/hyperswitch_connectors/src/connectors/placetopay.rs index 5eb93e6d5318..6e593c53e16d 100644 --- a/crates/router/src/connector/placetopay.rs +++ b/crates/hyperswitch_connectors/src/connectors/placetopay.rs @@ -1,29 +1,49 @@ pub mod transformers; +use api_models::webhooks::{IncomingWebhookEvent, ObjectReferenceId}; +use common_enums::enums; use common_utils::{ - request::RequestContent, + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, types::{AmountConvertor, MinorUnit, MinorUnitForConnector}, }; use error_stack::{report, ResultExt}; -use transformers as placetopay; - -use crate::{ - configs::settings, - connector::utils as connector_utils, - core::errors::{self, CustomResult}, - events::connector_api_logs::ConnectorEvent, - headers, - services::{ - self, - request::{self}, - ConnectorIntegration, ConnectorValidation, +use hyperswitch_domain_models::{ + router_data::{AccessToken, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, types::{ - self, - api::{self, enums, ConnectorCommon, ConnectorCommonExt}, - ErrorResponse, Response, + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, }, - utils::BytesExt, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, +}; +use transformers as placetopay; + +use crate::{ + constants::headers, + types::ResponseRouterData, + utils::{construct_not_supported_error_report, convert_amount}, }; #[derive(Clone)] @@ -52,12 +72,8 @@ impl api::RefundExecute for Placetopay {} impl api::RefundSync for Placetopay {} impl api::PaymentToken for Placetopay {} -impl - ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Placetopay +impl ConnectorIntegration + for Placetopay { // Not Implemented (R) } @@ -68,9 +84,9 @@ where { fn build_headers( &self, - req: &types::RouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let mut header = vec![( headers::CONTENT_TYPE.to_string(), self.get_content_type().to_string().into(), @@ -94,7 +110,7 @@ impl ConnectorCommon for Placetopay { "application/json" } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.placetopay.base_url.as_ref() } @@ -123,49 +139,37 @@ impl ConnectorCommon for Placetopay { } impl ConnectorValidation for Placetopay { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic => Ok(()), + enums::CaptureMethod::Automatic | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::Manual | enums::CaptureMethod::ManualMultiple - | enums::CaptureMethod::Scheduled => Err( - connector_utils::construct_not_supported_error_report(capture_method, self.id()), - ), + | enums::CaptureMethod::Scheduled => Err(construct_not_supported_error_report( + capture_method, + self.id(), + )), } } } -impl ConnectorIntegration - for Placetopay -{ -} +impl ConnectorIntegration for Placetopay {} -impl ConnectorIntegration - for Placetopay -{ -} +impl ConnectorIntegration for Placetopay {} -impl - ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Placetopay +impl ConnectorIntegration + for Placetopay { fn build_request( &self, - _req: &types::RouterData< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - >, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err( errors::ConnectorError::NotImplemented("Setup Mandate flow for Placetopay".to_string()) .into(), @@ -173,14 +177,12 @@ impl } } -impl ConnectorIntegration - for Placetopay -{ +impl ConnectorIntegration for Placetopay { fn get_headers( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -190,18 +192,18 @@ impl ConnectorIntegration CustomResult { Ok(format!("{}/process", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::PaymentsAuthorizeRouterData, - _connectors: &settings::Connectors, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, ) -> CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_amount, req.request.currency, @@ -213,12 +215,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsAuthorizeType::get_url( self, req, connectors, )?) @@ -235,10 +237,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: placetopay::PlacetopayPaymentsResponse = res .response .parse_struct("Placetopay PlacetopayPaymentsResponse") @@ -247,7 +249,7 @@ impl ConnectorIntegration - for Placetopay -{ +impl ConnectorIntegration for Placetopay { fn get_headers( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -280,16 +280,16 @@ impl ConnectorIntegration CustomResult { Ok(format!("{}/query", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::PaymentsSyncRouterData, - _connectors: &settings::Connectors, + req: &PaymentsSyncRouterData, + _connectors: &Connectors, ) -> CustomResult { let req_obj = placetopay::PlacetopayPsyncRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(req_obj))) @@ -297,12 +297,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) @@ -315,10 +315,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: placetopay::PlacetopayPaymentsResponse = res .response .parse_struct("placetopay PaymentsSyncResponse") @@ -327,7 +327,7 @@ impl ConnectorIntegration - for Placetopay -{ +impl ConnectorIntegration for Placetopay { fn get_headers( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -360,16 +358,16 @@ impl ConnectorIntegration CustomResult { Ok(format!("{}/transaction", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::PaymentsCaptureRouterData, - _connectors: &settings::Connectors, + req: &PaymentsCaptureRouterData, + _connectors: &Connectors, ) -> CustomResult { let req_obj = placetopay::PlacetopayNextActionRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(req_obj))) @@ -377,12 +375,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsCaptureType::get_headers( @@ -397,10 +395,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: placetopay::PlacetopayPaymentsResponse = res .response .parse_struct("Placetopay PaymentsCaptureResponse") @@ -409,7 +407,7 @@ impl ConnectorIntegration - for Placetopay -{ +impl ConnectorIntegration for Placetopay { fn get_headers( &self, - req: &types::PaymentsCancelRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -442,16 +438,16 @@ impl ConnectorIntegration CustomResult { Ok(format!("{}/transaction", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::PaymentsCancelRouterData, - _connectors: &settings::Connectors, + req: &PaymentsCancelRouterData, + _connectors: &Connectors, ) -> CustomResult { let req_obj = placetopay::PlacetopayNextActionRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(req_obj))) @@ -459,12 +455,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) @@ -477,10 +473,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: placetopay::PlacetopayPaymentsResponse = res .response .parse_struct("Placetopay PaymentCancelResponse") @@ -489,7 +485,7 @@ impl ConnectorIntegration - for Placetopay -{ +impl ConnectorIntegration for Placetopay { fn get_headers( &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -522,16 +516,16 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, + _req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!("{}/transaction", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::RefundsRouterData, - _connectors: &settings::Connectors, + req: &RefundsRouterData, + _connectors: &Connectors, ) -> CustomResult { let req_obj = placetopay::PlacetopayRefundRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(req_obj))) @@ -539,11 +533,11 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) .url(&types::RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundExecuteType::get_headers( @@ -558,10 +552,10 @@ impl ConnectorIntegration, + data: &RefundsRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult, errors::ConnectorError> { let response: placetopay::PlacetopayRefundResponse = res .response .parse_struct("placetopay PlacetopayRefundResponse") @@ -570,7 +564,7 @@ impl ConnectorIntegration - for Placetopay -{ +impl ConnectorIntegration for Placetopay { fn get_headers( &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -603,16 +595,16 @@ impl ConnectorIntegration CustomResult { Ok(format!("{}/query", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::RefundsRouterData, - _connectors: &settings::Connectors, + req: &RefundsRouterData, + _connectors: &Connectors, ) -> CustomResult { let req_obj = placetopay::PlacetopayRsyncRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(req_obj))) @@ -620,12 +612,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundSyncType::get_headers(self, req, connectors)?) @@ -638,10 +630,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: placetopay::PlacetopayRefundResponse = res .response .parse_struct("placetopay PlacetopayRefundResponse") @@ -650,7 +642,7 @@ impl ConnectorIntegration, - ) -> CustomResult { + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_event_type( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_resource_object( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for Placetopay {} diff --git a/crates/router/src/connector/placetopay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/placetopay/transformers.rs similarity index 81% rename from crates/router/src/connector/placetopay/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/placetopay/transformers.rs index 919fe25bdd8f..e51093c28d89 100644 --- a/crates/router/src/connector/placetopay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/placetopay/transformers.rs @@ -1,18 +1,25 @@ -use common_utils::{date_time, types::MinorUnit}; -use diesel_models::enums; +use common_enums::{enums, Currency}; +use common_utils::{consts::BASE64_ENGINE, date_time, types::MinorUnit}; use error_stack::ResultExt; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{ConnectorAuthType, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types, +}; +use hyperswitch_interfaces::errors; use masking::{PeekInterface, Secret}; use ring::digest; use serde::{Deserialize, Serialize}; use crate::{ - connector::utils::{ - self, BrowserInformationData, CardData, PaymentsAuthorizeRequestData, - PaymentsSyncRequestData, RouterData, + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::{ + self, generate_random_bytes, BrowserInformationData, CardData as _, + PaymentsAuthorizeRequestData, PaymentsSyncRequestData, RouterData as _, }, - consts, - core::errors, - types::{self, api, domain, storage::enums as storage_enums}, }; pub struct PlacetopayRouterData { @@ -72,7 +79,7 @@ pub struct PlacetopayPayment { #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct PlacetopayAmount { - currency: storage_enums::Currency, + currency: Currency, total: MinorUnit, } @@ -110,7 +117,7 @@ impl TryFrom<&PlacetopayRouterData<&types::PaymentsAuthorizeRouterData>> }, }; match item.router_data.request.payment_method_data.clone() { - domain::PaymentMethodData::Card(req_card) => { + PaymentMethodData::Card(req_card) => { let card = PlacetopayCard { number: req_card.card_number.clone(), expiration: req_card @@ -128,22 +135,24 @@ impl TryFrom<&PlacetopayRouterData<&types::PaymentsAuthorizeRouterData>> }, }) } - domain::PaymentMethodData::Wallet(_) - | 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::RealTimePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + PaymentMethodData::Wallet(_) + | PaymentMethodData::CardRedirect(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Placetopay"), ) @@ -153,11 +162,11 @@ impl TryFrom<&PlacetopayRouterData<&types::PaymentsAuthorizeRouterData>> } } -impl TryFrom<&types::ConnectorAuthType> for PlacetopayAuth { +impl TryFrom<&ConnectorAuthType> for PlacetopayAuth { type Error = error_stack::Report; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { + fn try_from(auth_type: &ConnectorAuthType) -> Result { let placetopay_auth = PlacetopayAuthType::try_from(auth_type)?; - let nonce_bytes = utils::generate_random_bytes(16); + let nonce_bytes = generate_random_bytes(16); let now = date_time::date_as_yyyymmddthhmmssmmmz() .change_context(errors::ConnectorError::RequestEncodingFailed)?; let seed = format!("{}+00:00", now.split_at(now.len() - 5).0); @@ -165,8 +174,8 @@ impl TryFrom<&types::ConnectorAuthType> for PlacetopayAuth { context.update(&nonce_bytes); context.update(seed.as_bytes()); context.update(placetopay_auth.tran_key.peek().as_bytes()); - let encoded_digest = base64::Engine::encode(&consts::BASE64_ENGINE, context.finish()); - let nonce = Secret::new(base64::Engine::encode(&consts::BASE64_ENGINE, &nonce_bytes)); + let encoded_digest = base64::Engine::encode(&BASE64_ENGINE, context.finish()); + let nonce = Secret::new(base64::Engine::encode(&BASE64_ENGINE, &nonce_bytes)); Ok(Self { login: placetopay_auth.login, tran_key: encoded_digest.into(), @@ -176,11 +185,11 @@ impl TryFrom<&types::ConnectorAuthType> for PlacetopayAuth { } } -impl TryFrom<&types::ConnectorAuthType> for PlacetopayAuthType { +impl TryFrom<&ConnectorAuthType> for PlacetopayAuthType { type Error = error_stack::Report; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { - if let types::ConnectorAuthType::BodyKey { api_key, key1 } = auth_type { + fn try_from(auth_type: &ConnectorAuthType) -> Result { + if let ConnectorAuthType::BodyKey { api_key, key1 } = auth_type { Ok(Self { login: api_key.to_owned(), tran_key: key1.to_owned(), @@ -242,28 +251,21 @@ pub struct PlacetopayPaymentsResponse { authorization: Option, } -impl - TryFrom< - types::ResponseRouterData, - > for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< - F, - PlacetopayPaymentsResponse, - T, - types::PaymentsResponseData, - >, + item: ResponseRouterData, ) -> Result { Ok(Self { status: enums::AttemptStatus::from(item.response.status.status), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( item.response.internal_reference.to_string(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: item .response .authorization @@ -372,15 +374,15 @@ pub struct PlacetopayRefundResponse { internal_reference: u64, } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item.response.internal_reference.to_string(), refund_status: enums::RefundStatus::from(item.response.status.status), }), @@ -396,9 +398,9 @@ pub struct PlacetopayRsyncRequest { internal_reference: u64, } -impl TryFrom<&types::RefundsRouterData> for PlacetopayRsyncRequest { +impl TryFrom<&types::RefundsRouterData> for PlacetopayRsyncRequest { type Error = error_stack::Report; - fn try_from(item: &types::RefundsRouterData) -> Result { + fn try_from(item: &types::RefundsRouterData) -> Result { let auth = PlacetopayAuth::try_from(&item.connector_auth_type)?; let internal_reference = item .request @@ -412,15 +414,15 @@ impl TryFrom<&types::RefundsRouterData> for PlacetopayRsyncRequest { } } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item.response.internal_reference.to_string(), refund_status: enums::RefundStatus::from(item.response.status.status), }), diff --git a/crates/hyperswitch_connectors/src/connectors/powertranz.rs b/crates/hyperswitch_connectors/src/connectors/powertranz.rs index a437d419bc83..80a35590520c 100644 --- a/crates/hyperswitch_connectors/src/connectors/powertranz.rs +++ b/crates/hyperswitch_connectors/src/connectors/powertranz.rs @@ -30,7 +30,10 @@ use hyperswitch_domain_models::{ }, }; use hyperswitch_interfaces::{ - api::{self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorValidation}, + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, configs::Connectors, consts, errors, events::connector_api_logs::ConnectorEvent, @@ -145,14 +148,17 @@ impl ConnectorCommon for Powertranz { } impl ConnectorValidation for Powertranz { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( utils::construct_not_supported_error_report(capture_method, self.id()), ), @@ -611,3 +617,5 @@ impl webhooks::IncomingWebhook for Powertranz { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for Powertranz {} diff --git a/crates/hyperswitch_connectors/src/connectors/powertranz/transformers.rs b/crates/hyperswitch_connectors/src/connectors/powertranz/transformers.rs index 5ea64f8b9587..83f861dfdb35 100644 --- a/crates/hyperswitch_connectors/src/connectors/powertranz/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/powertranz/transformers.rs @@ -1,4 +1,4 @@ -use common_enums::enums::{self, AuthenticationType, Currency}; +use common_enums::enums::{self, AuthenticationType}; use common_utils::pii::IpAddress; use hyperswitch_domain_models::{ payment_method_data::{Card, PaymentMethodData}, @@ -121,16 +121,20 @@ impl TryFrom<&PaymentsAuthorizeRouterData> for PowertranzPaymentsRequest { | PaymentMethodData::MandatePayment | PaymentMethodData::Reward | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) | PaymentMethodData::Upi(_) | PaymentMethodData::Voucher(_) | PaymentMethodData::GiftCard(_) | PaymentMethodData::OpenBanking(_) | PaymentMethodData::CardToken(_) - | PaymentMethodData::NetworkToken(_) => Err(errors::ConnectorError::NotSupported { - message: utils::SELECTED_PAYMENT_METHOD.to_string(), - connector: "powertranz", + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + Err(errors::ConnectorError::NotSupported { + message: utils::SELECTED_PAYMENT_METHOD.to_string(), + connector: "powertranz", + } + .into()) } - .into()), }?; // let billing_address = get_address_details(&item.address.billing, &item.request.email); // let shipping_address = get_address_details(&item.address.shipping, &item.request.email); @@ -144,7 +148,7 @@ impl TryFrom<&PaymentsAuthorizeRouterData> for PowertranzPaymentsRequest { item.request.amount, item.request.currency, )?, - currency_code: Currency::iso_4217(&item.request.currency).to_string(), + currency_code: item.request.currency.iso_4217().to_string(), three_d_secure, source, order_identifier: item.connector_request_reference_id.clone(), @@ -333,8 +337,8 @@ impl TryFrom for Prophetpay +impl ConnectorIntegration + for Prophetpay { // Not Implemented (R) } @@ -59,9 +75,9 @@ where { fn build_headers( &self, - req: &types::RouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let mut header = vec![( headers::CONTENT_TYPE.to_string(), self.get_content_type().to_string().into(), @@ -85,19 +101,19 @@ impl ConnectorCommon for Prophetpay { "application/json" } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.prophetpay.base_url.as_ref() } fn get_auth_header( &self, - auth_type: &types::ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { let auth = prophetpay::ProphetpayAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; let auth_val = format!("{}:{}", auth.user_name.peek(), auth.password.peek()); - let basic_token = format!("Basic {}", consts::BASE64_ENGINE.encode(auth_val)); + let basic_token = format!("Basic {}", BASE64_ENGINE.encode(auth_val)); Ok(vec![( headers::AUTHORIZATION.to_string(), @@ -120,8 +136,8 @@ impl ConnectorCommon for Prophetpay { Ok(ErrorResponse { status_code: res.status_code, - code: consts::NO_ERROR_CODE.to_string(), - message: consts::NO_ERROR_MESSAGE.to_string(), + code: NO_ERROR_CODE.to_string(), + message: NO_ERROR_MESSAGE.to_string(), reason: Some(response.to_string()), attempt_status: None, connector_transaction_id: None, @@ -133,33 +149,20 @@ impl ConnectorValidation for Prophetpay { //TODO: implement functions when support enabled } -impl ConnectorIntegration - for Prophetpay -{ +impl ConnectorIntegration for Prophetpay { //TODO: implement sessions flow } -impl ConnectorIntegration - for Prophetpay -{ -} +impl ConnectorIntegration for Prophetpay {} -impl - ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Prophetpay +impl ConnectorIntegration + for Prophetpay { fn build_request( &self, - _req: &types::RouterData< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - >, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err( errors::ConnectorError::NotImplemented("Setup Mandate flow for Prophetpay".to_string()) .into(), @@ -167,14 +170,12 @@ impl } } -impl ConnectorIntegration - for Prophetpay -{ +impl ConnectorIntegration for Prophetpay { fn get_headers( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -184,8 +185,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}hp/api/HostedTokenize/CreateHostedTokenize", @@ -195,8 +196,8 @@ impl ConnectorIntegration CustomResult { let connector_router_data = prophetpay::ProphetpayRouterData::try_from(( &self.get_currency_unit(), @@ -211,12 +212,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsAuthorizeType::get_url( self, req, connectors, )?) @@ -233,12 +234,12 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult + ) -> CustomResult where - types::PaymentsResponseData: Clone, + PaymentsResponseData: Clone, { let response: prophetpay::ProphetpayTokenResponse = res .response @@ -248,7 +249,7 @@ impl ConnectorIntegration for Prophetpay +impl ConnectorIntegration + for Prophetpay { fn get_headers( &self, - req: &types::PaymentsCompleteAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -285,8 +282,8 @@ impl fn get_url( &self, - _req: &types::PaymentsCompleteAuthorizeRouterData, - connectors: &settings::Connectors, + _req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!( "{}hp/api/Transactions/ProcessTransaction", @@ -296,8 +293,8 @@ impl fn get_request_body( &self, - req: &types::PaymentsCompleteAuthorizeRouterData, - _connectors: &settings::Connectors, + req: &PaymentsCompleteAuthorizeRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_router_data = prophetpay::ProphetpayRouterData::try_from(( &self.get_currency_unit(), @@ -313,12 +310,12 @@ impl fn build_request( &self, - req: &types::PaymentsCompleteAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsCompleteAuthorizeType::get_url( self, req, connectors, )?) @@ -335,12 +332,12 @@ impl fn handle_response( &self, - data: &types::PaymentsCompleteAuthorizeRouterData, + data: &PaymentsCompleteAuthorizeRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult + ) -> CustomResult where - types::PaymentsResponseData: Clone, + PaymentsResponseData: Clone, { let response: prophetpay::ProphetpayCompleteAuthResponse = res .response @@ -350,7 +347,7 @@ impl event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -366,14 +363,12 @@ impl } } -impl ConnectorIntegration - for Prophetpay -{ +impl ConnectorIntegration for Prophetpay { fn get_headers( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -383,8 +378,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}hp/api/Transactions/ProcessTransaction", @@ -394,8 +389,8 @@ impl ConnectorIntegration CustomResult { let connector_req = prophetpay::ProphetpaySyncRequest::try_from(req)?; @@ -404,12 +399,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) @@ -422,10 +417,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: prophetpay::ProphetpaySyncResponse = res .response .parse_struct("prophetpay PaymentsSyncResponse") @@ -434,7 +429,7 @@ impl ConnectorIntegration - for Prophetpay -{ -} +impl ConnectorIntegration for Prophetpay {} // This is Void Implementation for Prophetpay // Since Prophetpay does not have capture this have been commented out but kept if it is required for future usage -impl ConnectorIntegration - for Prophetpay -{ +impl ConnectorIntegration for Prophetpay { /* fn get_headers( &self, @@ -497,9 +487,9 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + _req: &PaymentsCancelRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err(errors::ConnectorError::NotImplemented("Void flow not implemented".to_string()).into()) } @@ -530,14 +520,12 @@ impl ConnectorIntegration - for Prophetpay -{ +impl ConnectorIntegration for Prophetpay { fn get_headers( &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -547,8 +535,8 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, + _req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!( "{}hp/api/Transactions/ProcessTransaction", @@ -558,8 +546,8 @@ impl ConnectorIntegration, - _connectors: &settings::Connectors, + req: &RefundsRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_router_data = prophetpay::ProphetpayRouterData::try_from(( &self.get_currency_unit(), @@ -573,11 +561,11 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) .url(&types::RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundExecuteType::get_headers( @@ -592,10 +580,10 @@ impl ConnectorIntegration, + data: &RefundsRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult, errors::ConnectorError> { let response: prophetpay::ProphetpayRefundResponse = res .response .parse_struct("prophetpay ProphetpayRefundResponse") @@ -604,7 +592,7 @@ impl ConnectorIntegration - for Prophetpay -{ +impl ConnectorIntegration for Prophetpay { fn get_headers( &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -637,8 +623,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}hp/api/Transactions/ProcessTransaction", @@ -648,8 +634,8 @@ impl ConnectorIntegration CustomResult { let connector_req = prophetpay::ProphetpayRefundSyncRequest::try_from(req)?; @@ -658,12 +644,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundSyncType::get_headers(self, req, connectors)?) @@ -676,10 +662,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: prophetpay::ProphetpayRefundSyncResponse = res .response .parse_struct("prophetpay ProphetpayRefundResponse") @@ -688,7 +674,7 @@ impl ConnectorIntegration, - ) -> CustomResult { + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_event_type( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_resource_object( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for Prophetpay {} diff --git a/crates/router/src/connector/prophetpay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/prophetpay/transformers.rs similarity index 82% rename from crates/router/src/connector/prophetpay/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/prophetpay/transformers.rs index 6862c7c4c807..53f902ff4956 100644 --- a/crates/router/src/connector/prophetpay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/prophetpay/transformers.rs @@ -1,17 +1,30 @@ use std::collections::HashMap; -use common_utils::{consts, errors::CustomResult}; +use common_enums::enums; +use common_utils::{ + consts::{PROPHETPAY_REDIRECT_URL, PROPHETPAY_TOKEN}, + errors::CustomResult, + request::Method, +}; use error_stack::ResultExt; +use hyperswitch_domain_models::{ + payment_method_data::{CardRedirectData, PaymentMethodData}, + router_data::{ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::refunds::Execute, + router_request_types::{ + CompleteAuthorizeData, CompleteAuthorizeRedirectResponse, PaymentsAuthorizeData, ResponseId, + }, + router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData}, + types, +}; +use hyperswitch_interfaces::{api, consts::NO_ERROR_CODE, errors}; use masking::{ExposeInterface, PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use url::Url; use crate::{ - connector::utils::{self, to_connector_meta}, - consts as const_val, - core::errors, - services, - types::{self, api, domain, storage::enums}, + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::{self, to_connector_meta}, }; pub struct ProphetpayRouterData { @@ -38,11 +51,11 @@ pub struct ProphetpayAuthType { pub(super) profile_id: Secret, } -impl TryFrom<&types::ConnectorAuthType> for ProphetpayAuthType { +impl TryFrom<&ConnectorAuthType> for ProphetpayAuthType { type Error = error_stack::Report; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { + fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - types::ConnectorAuthType::SignatureKey { + ConnectorAuthType::SignatureKey { api_key, key1, api_secret, @@ -124,9 +137,7 @@ impl TryFrom<&ProphetpayRouterData<&types::PaymentsAuthorizeRouterData>> ) -> Result { if item.router_data.request.currency == api_models::enums::Currency::USD { match item.router_data.request.payment_method_data.clone() { - domain::PaymentMethodData::CardRedirect( - domain::payments::CardRedirectData::CardRedirect {}, - ) => { + PaymentMethodData::CardRedirect(CardRedirectData::CardRedirect {}) => { let auth_data = ProphetpayAuthType::try_from(&item.router_data.connector_auth_type)?; Ok(Self { @@ -165,26 +176,21 @@ pub struct ProphetpayTokenResponse { impl TryFrom< - types::ResponseRouterData< - F, - ProphetpayTokenResponse, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, - >, - > for types::RouterData + ResponseRouterData, + > for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< + item: ResponseRouterData< F, ProphetpayTokenResponse, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, + PaymentsAuthorizeData, + PaymentsResponseData, >, ) -> Result { let url_data = format!( "{}{}", - consts::PROPHETPAY_REDIRECT_URL, + PROPHETPAY_REDIRECT_URL, item.response.hosted_tokenize_id.expose() ); @@ -199,10 +205,10 @@ impl Ok(Self { status: enums::AttemptStatus::AuthenticationPending, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::NoResponseId, - redirection_data, - mandate_reference: None, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::NoResponseId, + redirection_data: Box::new(redirection_data), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -217,7 +223,7 @@ impl fn get_redirect_url_form( mut redirect_url: Url, complete_auth_url: Option, -) -> CustomResult { +) -> CustomResult { let mut form_fields = HashMap::::new(); form_fields.insert( @@ -230,9 +236,9 @@ fn get_redirect_url_form( // Do not include query params in the endpoint redirect_url.set_query(None); - Ok(services::RedirectForm::Form { + Ok(RedirectForm::Form { endpoint: redirect_url.to_string(), - method: services::Method::Get, + method: Method::Get, form_fields, }) } @@ -271,7 +277,7 @@ impl TryFrom<&ProphetpayRouterData<&types::PaymentsCompleteAuthorizeRouterData>> } fn get_card_token( - response: Option, + response: Option, ) -> CustomResult { let res = response.ok_or(errors::ConnectorError::MissingRequiredField { field_name: "redirect_response", @@ -298,7 +304,7 @@ fn get_card_token( .ok_or(errors::ConnectorError::ResponseDeserializationFailed)?; for (key, val) in queries_params { - if key.as_str() == consts::PROPHETPAY_TOKEN { + if key.as_str() == PROPHETPAY_TOKEN { return Ok(val); } } @@ -372,21 +378,21 @@ pub struct ProphetpayCardTokenData { impl TryFrom< - types::ResponseRouterData< + ResponseRouterData< F, ProphetpayCompleteAuthResponse, - types::CompleteAuthorizeData, - types::PaymentsResponseData, + CompleteAuthorizeData, + PaymentsResponseData, >, - > for types::RouterData + > for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< + item: ResponseRouterData< F, ProphetpayCompleteAuthResponse, - types::CompleteAuthorizeData, - types::PaymentsResponseData, + CompleteAuthorizeData, + PaymentsResponseData, >, ) -> Result { if item.response.success { @@ -397,12 +403,10 @@ impl let connector_metadata = serde_json::to_value(card_token_data).ok(); Ok(Self { status: enums::AttemptStatus::Charged, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - item.response.transaction_id, - ), - redirection_data: None, - mandate_reference: None, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.transaction_id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata, network_txn_id: None, connector_response_reference_id: None, @@ -414,7 +418,7 @@ impl } else { Ok(Self { status: enums::AttemptStatus::Failure, - response: Err(types::ErrorResponse { + response: Err(ErrorResponse { code: item.response.response_code, message: item.response.response_text.clone(), reason: Some(item.response.response_text), @@ -437,23 +441,20 @@ pub struct ProphetpaySyncResponse { pub transaction_id: String, } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { if item.response.success { Ok(Self { status: enums::AttemptStatus::Charged, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - item.response.transaction_id, - ), - redirection_data: None, - mandate_reference: None, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.transaction_id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -465,8 +466,8 @@ impl } else { Ok(Self { status: enums::AttemptStatus::Failure, - response: Err(types::ErrorResponse { - code: const_val::NO_ERROR_CODE.to_string(), + response: Err(ErrorResponse { + code: NO_ERROR_CODE.to_string(), message: item.response.response_text.clone(), reason: Some(item.response.response_text), status_code: item.http_code, @@ -488,23 +489,20 @@ pub struct ProphetpayVoidResponse { pub transaction_id: String, } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { if item.response.success { Ok(Self { status: enums::AttemptStatus::Voided, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - item.response.transaction_id, - ), - redirection_data: None, - mandate_reference: None, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.transaction_id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -516,8 +514,8 @@ impl } else { Ok(Self { status: enums::AttemptStatus::VoidFailed, - response: Err(types::ErrorResponse { - code: const_val::NO_ERROR_CODE.to_string(), + response: Err(ErrorResponse { + code: NO_ERROR_CODE.to_string(), message: item.response.response_text.clone(), reason: Some(item.response.response_text), status_code: item.http_code, @@ -601,16 +599,16 @@ pub struct ProphetpayRefundResponse { pub tran_seq_number: Option, } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { if item.response.success { Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { // no refund id is generated, tranSeqNumber is kept for future usage connector_refund_id: item.response.tran_seq_number.ok_or( errors::ConnectorError::MissingRequiredField { @@ -624,8 +622,8 @@ impl TryFrom TryFrom> +impl TryFrom> for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { if item.response.success { Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { // no refund id is generated, rather transaction id is used for referring to status in refund also connector_refund_id: item.data.request.connector_transaction_id.clone(), refund_status: enums::RefundStatus::Success, @@ -664,8 +662,8 @@ impl TryFrom + Sync), +} +impl Rapyd { + pub fn new() -> &'static Self { + &Self { + amount_converter: &MinorUnitForConnector, + } + } +} impl Rapyd { pub fn generate_signature( &self, @@ -44,7 +71,7 @@ impl Rapyd { http_method: &str, url_path: &str, body: &str, - timestamp: &i64, + timestamp: i64, salt: &str, ) -> CustomResult { let rapyd::RapydAuthType { @@ -59,7 +86,7 @@ impl Rapyd { let key = hmac::Key::new(hmac::HMAC_SHA256, secret_key.peek().as_bytes()); let tag = hmac::sign(&key, to_sign.as_bytes()); let hmac_sign = hex::encode(tag); - let signature_value = consts::BASE64_ENGINE_URL_SAFE.encode(hmac_sign); + let signature_value = BASE64_ENGINE_URL_SAFE.encode(hmac_sign); Ok(signature_value) } } @@ -77,20 +104,20 @@ impl ConnectorCommon for Rapyd { "application/json" } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.rapyd.base_url.as_ref() } fn get_auth_header( &self, - _auth_type: &types::ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { + _auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { Ok(vec![]) } fn build_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { let response: Result< @@ -121,16 +148,19 @@ impl ConnectorCommon for Rapyd { } impl ConnectorValidation for Rapyd { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( - connector_utils::construct_not_supported_error_report(capture_method, self.id()), + construct_not_supported_error_report(capture_method, self.id()), ), } } @@ -140,39 +170,22 @@ impl api::ConnectorAccessToken for Rapyd {} impl api::PaymentToken for Rapyd {} -impl - services::ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Rapyd +impl ConnectorIntegration + for Rapyd { // Not Implemented (R) } -impl - services::ConnectorIntegration< - api::AccessTokenAuth, - types::AccessTokenRequestData, - types::AccessToken, - > for Rapyd -{ -} +impl ConnectorIntegration for Rapyd {} impl api::PaymentAuthorize for Rapyd {} -impl - services::ConnectorIntegration< - api::Authorize, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, - > for Rapyd -{ +impl ConnectorIntegration for Rapyd { fn get_headers( &self, - _req: &types::PaymentsAuthorizeRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + _req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { Ok(vec![( headers::CONTENT_TYPE.to_string(), types::PaymentsAuthorizeType::get_content_type(self) @@ -187,36 +200,32 @@ impl fn get_url( &self, - _req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, + _req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!("{}/v1/payments", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::PaymentsAuthorizeRouterData, - _connectors: &settings::Connectors, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, ) -> CustomResult { - let connector_router_data = rapyd::RapydRouterData::try_from(( - &self.get_currency_unit(), + let amount = convert_amount( + self.amount_converter, + req.request.minor_amount, req.request.currency, - req.request.amount, - req, - ))?; + )?; + let connector_router_data = rapyd::RapydRouterData::from((amount, req)); let connector_req = rapyd::RapydPaymentsRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, - req: &types::RouterData< - api::Authorize, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, - >, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &RouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { let timestamp = date_time::now_unix_timestamp(); let salt = Alphanumeric.sample_string(&mut rand::thread_rng(), 12); @@ -224,15 +233,15 @@ impl let body = types::PaymentsAuthorizeType::get_request_body(self, req, connectors)?; let req_body = body.get_inner_value().expose(); let signature = - self.generate_signature(&auth, "post", "/v1/payments", &req_body, ×tamp, &salt)?; + self.generate_signature(&auth, "post", "/v1/payments", &req_body, timestamp, &salt)?; let headers = vec![ ("access_key".to_string(), auth.access_key.into_masked()), ("salt".to_string(), salt.into_masked()), ("timestamp".to_string(), timestamp.to_string().into()), ("signature".to_string(), signature.into_masked()), ]; - let request = services::RequestBuilder::new() - .method(services::Method::Post) + let request = RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsAuthorizeType::get_url( self, req, connectors, )?) @@ -250,17 +259,17 @@ impl fn handle_response( &self, - data: &types::PaymentsAuthorizeRouterData, + data: &PaymentsAuthorizeRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: rapyd::RapydPaymentsResponse = res .response .parse_struct("Rapyd PaymentResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -270,7 +279,7 @@ impl fn get_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { self.build_error_response(res, event_builder) @@ -280,22 +289,12 @@ impl impl api::Payment for Rapyd {} impl api::MandateSetup for Rapyd {} -impl - services::ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Rapyd -{ +impl ConnectorIntegration for Rapyd { fn build_request( &self, - _req: &types::RouterData< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - >, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err( errors::ConnectorError::NotImplemented("Setup Mandate flow for Rapyd".to_string()) .into(), @@ -305,18 +304,12 @@ impl impl api::PaymentVoid for Rapyd {} -impl - services::ConnectorIntegration< - api::Void, - types::PaymentsCancelData, - types::PaymentsResponseData, - > for Rapyd -{ +impl ConnectorIntegration for Rapyd { fn get_headers( &self, - _req: &types::PaymentsCancelRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + _req: &PaymentsCancelRouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { Ok(vec![( headers::CONTENT_TYPE.to_string(), types::PaymentsVoidType::get_content_type(self) @@ -331,8 +324,8 @@ impl fn get_url( &self, - req: &types::PaymentsCancelRouterData, - connectors: &settings::Connectors, + req: &PaymentsCancelRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!( "{}/v1/payments/{}", @@ -343,16 +336,16 @@ impl fn build_request( &self, - req: &types::PaymentsCancelRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { let timestamp = date_time::now_unix_timestamp(); let salt = Alphanumeric.sample_string(&mut rand::thread_rng(), 12); let auth: rapyd::RapydAuthType = rapyd::RapydAuthType::try_from(&req.connector_auth_type)?; let url_path = format!("/v1/payments/{}", req.request.connector_transaction_id); let signature = - self.generate_signature(&auth, "delete", &url_path, "", ×tamp, &salt)?; + self.generate_signature(&auth, "delete", &url_path, "", timestamp, &salt)?; let headers = vec![ ("access_key".to_string(), auth.access_key.into_masked()), @@ -360,8 +353,8 @@ impl ("timestamp".to_string(), timestamp.to_string().into()), ("signature".to_string(), signature.into_masked()), ]; - let request = services::RequestBuilder::new() - .method(services::Method::Delete) + let request = RequestBuilder::new() + .method(Method::Delete) .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) @@ -372,17 +365,17 @@ impl fn handle_response( &self, - data: &types::PaymentsCancelRouterData, + data: &PaymentsCancelRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: rapyd::RapydPaymentsResponse = res .response .parse_struct("Rapyd PaymentResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -392,7 +385,7 @@ impl fn get_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { self.build_error_response(res, event_builder) @@ -400,15 +393,12 @@ impl } impl api::PaymentSync for Rapyd {} -impl - services::ConnectorIntegration - for Rapyd -{ +impl ConnectorIntegration for Rapyd { fn get_headers( &self, - _req: &types::PaymentsSyncRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + _req: &PaymentsSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { Ok(vec![( headers::CONTENT_TYPE.to_string(), types::PaymentsSyncType::get_content_type(self) @@ -423,8 +413,8 @@ impl fn get_url( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, + req: &PaymentsSyncRouterData, + connectors: &Connectors, ) -> CustomResult { let id = req.request.connector_transaction_id.clone(); Ok(format!( @@ -437,9 +427,9 @@ impl fn build_request( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { let timestamp = date_time::now_unix_timestamp(); let salt = Alphanumeric.sample_string(&mut rand::thread_rng(), 12); @@ -451,7 +441,7 @@ impl .get_connector_transaction_id() .change_context(errors::ConnectorError::MissingConnectorTransactionID)? ); - let signature = self.generate_signature(&auth, "get", &url_path, "", ×tamp, &salt)?; + let signature = self.generate_signature(&auth, "get", &url_path, "", timestamp, &salt)?; let headers = vec![ ("access_key".to_string(), auth.access_key.into_masked()), @@ -459,8 +449,8 @@ impl ("timestamp".to_string(), timestamp.to_string().into()), ("signature".to_string(), signature.into_masked()), ]; - let request = services::RequestBuilder::new() - .method(services::Method::Get) + let request = RequestBuilder::new() + .method(Method::Get) .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) @@ -471,7 +461,7 @@ impl fn get_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { self.build_error_response(res, event_builder) @@ -479,17 +469,17 @@ impl fn handle_response( &self, - data: &types::PaymentsSyncRouterData, + data: &PaymentsSyncRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: rapyd::RapydPaymentsResponse = res .response .parse_struct("Rapyd PaymentResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -499,18 +489,12 @@ impl } impl api::PaymentCapture for Rapyd {} -impl - services::ConnectorIntegration< - api::Capture, - types::PaymentsCaptureData, - types::PaymentsResponseData, - > for Rapyd -{ +impl ConnectorIntegration for Rapyd { fn get_headers( &self, - _req: &types::PaymentsCaptureRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { Ok(vec![( headers::CONTENT_TYPE.to_string(), types::PaymentsCaptureType::get_content_type(self) @@ -525,24 +509,24 @@ impl fn get_request_body( &self, - req: &types::PaymentsCaptureRouterData, - _connectors: &settings::Connectors, + req: &PaymentsCaptureRouterData, + _connectors: &Connectors, ) -> CustomResult { - let connector_router_data = rapyd::RapydRouterData::try_from(( - &self.get_currency_unit(), + let amount = convert_amount( + self.amount_converter, + req.request.minor_amount_to_capture, req.request.currency, - req.request.amount_to_capture, - req, - ))?; + )?; + let connector_router_data = rapyd::RapydRouterData::from((amount, req)); let connector_req = rapyd::CaptureRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { let timestamp = date_time::now_unix_timestamp(); let salt = Alphanumeric.sample_string(&mut rand::thread_rng(), 12); @@ -554,15 +538,15 @@ impl let body = types::PaymentsCaptureType::get_request_body(self, req, connectors)?; let req_body = body.get_inner_value().expose(); let signature = - self.generate_signature(&auth, "post", &url_path, &req_body, ×tamp, &salt)?; + self.generate_signature(&auth, "post", &url_path, &req_body, timestamp, &salt)?; let headers = vec![ ("access_key".to_string(), auth.access_key.into_masked()), ("salt".to_string(), salt.into_masked()), ("timestamp".to_string(), timestamp.to_string().into()), ("signature".to_string(), signature.into_masked()), ]; - let request = services::RequestBuilder::new() - .method(services::Method::Post) + let request = RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsCaptureType::get_headers( @@ -578,10 +562,10 @@ impl fn handle_response( &self, - data: &types::PaymentsCaptureRouterData, + data: &PaymentsCaptureRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: rapyd::RapydPaymentsResponse = res .response .parse_struct("RapydPaymentResponse") @@ -590,7 +574,7 @@ impl event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -600,8 +584,8 @@ impl fn get_url( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!( "{}/v1/payments/{}/capture", @@ -612,7 +596,7 @@ impl fn get_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { self.build_error_response(res, event_builder) @@ -621,13 +605,7 @@ impl impl api::PaymentSession for Rapyd {} -impl - services::ConnectorIntegration< - api::Session, - types::PaymentsSessionData, - types::PaymentsResponseData, - > for Rapyd -{ +impl ConnectorIntegration for Rapyd { //TODO: implement sessions flow } @@ -635,14 +613,12 @@ impl api::Refund for Rapyd {} impl api::RefundExecute for Rapyd {} impl api::RefundSync for Rapyd {} -impl services::ConnectorIntegration - for Rapyd -{ +impl ConnectorIntegration for Rapyd { fn get_headers( &self, - _req: &types::RefundsRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + _req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { Ok(vec![( headers::CONTENT_TYPE.to_string(), types::RefundExecuteType::get_content_type(self) @@ -657,23 +633,23 @@ impl services::ConnectorIntegration, - connectors: &settings::Connectors, + _req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!("{}/v1/refunds", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::RefundsRouterData, - _connectors: &settings::Connectors, + req: &RefundsRouterData, + _connectors: &Connectors, ) -> CustomResult { - let connector_router_data = rapyd::RapydRouterData::try_from(( - &self.get_currency_unit(), + let amount = convert_amount( + self.amount_converter, + req.request.minor_refund_amount, req.request.currency, - req.request.refund_amount, - req, - ))?; + )?; + let connector_router_data = rapyd::RapydRouterData::from((amount, req)); let connector_req = rapyd::RapydRefundRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -681,9 +657,9 @@ impl services::ConnectorIntegration, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { let timestamp = date_time::now_unix_timestamp(); let salt = Alphanumeric.sample_string(&mut rand::thread_rng(), 12); @@ -691,15 +667,15 @@ impl services::ConnectorIntegration, + data: &RefundsRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult, errors::ConnectorError> { + res: Response, + ) -> CustomResult, errors::ConnectorError> { let response: rapyd::RefundResponse = res .response .parse_struct("rapyd RefundResponse") .change_context(errors::ConnectorError::RequestEncodingFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -732,30 +708,28 @@ impl services::ConnectorIntegration, ) -> CustomResult { self.build_error_response(res, event_builder) } } -impl services::ConnectorIntegration - for Rapyd -{ +impl ConnectorIntegration for Rapyd { // default implementation of build_request method will be executed fn handle_response( &self, - data: &types::RefundSyncRouterData, + data: &RefundSyncRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: rapyd::RefundResponse = res .response .parse_struct("rapyd RefundResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -765,21 +739,21 @@ impl services::ConnectorIntegration, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Ok(Box::new(crypto::HmacSha256)) } fn get_webhook_source_verification_signature( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { - let base64_signature = connector_utils::get_header_key_value("signature", request.headers)?; - let signature = consts::BASE64_ENGINE_URL_SAFE + let base64_signature = get_header_key_value("signature", request.headers)?; + let signature = BASE64_ENGINE_URL_SAFE .decode(base64_signature.as_bytes()) .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; Ok(signature) @@ -787,18 +761,18 @@ impl api::IncomingWebhook for Rapyd { fn get_webhook_source_verification_message( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, merchant_id: &common_utils::id_type::MerchantId, connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { - let host = connector_utils::get_header_key_value("host", request.headers)?; + let host = get_header_key_value("host", request.headers)?; let connector = self.id(); let url_path = format!( "https://{host}/webhooks/{}/{connector}", merchant_id.get_string_repr() ); - let salt = connector_utils::get_header_key_value("salt", request.headers)?; - let timestamp = connector_utils::get_header_key_value("timestamp", request.headers)?; + let salt = get_header_key_value("salt", request.headers)?; + let timestamp = get_header_key_value("timestamp", request.headers)?; let stringify_auth = String::from_utf8(connector_webhook_secrets.secret.to_vec()) .change_context(errors::ConnectorError::WebhookSourceVerificationFailed) .attach_printable("Could not convert secret to UTF-8")?; @@ -821,7 +795,7 @@ impl api::IncomingWebhook for Rapyd { async fn verify_webhook_source( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, merchant_id: &common_utils::id_type::MerchantId, connector_webhook_details: Option, _connector_account_details: crypto::Encryptable>, @@ -861,7 +835,7 @@ impl api::IncomingWebhook for Rapyd { fn get_webhook_object_reference_id( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult { let webhook: transformers::RapydIncomingWebhook = request .body @@ -891,8 +865,8 @@ impl api::IncomingWebhook for Rapyd { fn get_webhook_event_type( &self, - request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { let webhook: transformers::RapydIncomingWebhook = request .body .parse_struct("RapydIncomingWebhook") @@ -900,34 +874,32 @@ impl api::IncomingWebhook for Rapyd { Ok(match webhook.webhook_type { rapyd::RapydWebhookObjectEventType::PaymentCompleted | rapyd::RapydWebhookObjectEventType::PaymentCaptured => { - api::IncomingWebhookEvent::PaymentIntentSuccess + IncomingWebhookEvent::PaymentIntentSuccess } rapyd::RapydWebhookObjectEventType::PaymentFailed => { - api::IncomingWebhookEvent::PaymentIntentFailure + IncomingWebhookEvent::PaymentIntentFailure } rapyd::RapydWebhookObjectEventType::PaymentRefundFailed | rapyd::RapydWebhookObjectEventType::PaymentRefundRejected => { - api::IncomingWebhookEvent::RefundFailure + IncomingWebhookEvent::RefundFailure } rapyd::RapydWebhookObjectEventType::RefundCompleted => { - api::IncomingWebhookEvent::RefundSuccess + IncomingWebhookEvent::RefundSuccess } rapyd::RapydWebhookObjectEventType::PaymentDisputeCreated => { - api::IncomingWebhookEvent::DisputeOpened - } - rapyd::RapydWebhookObjectEventType::Unknown => { - api::IncomingWebhookEvent::EventNotSupported + IncomingWebhookEvent::DisputeOpened } + rapyd::RapydWebhookObjectEventType::Unknown => IncomingWebhookEvent::EventNotSupported, rapyd::RapydWebhookObjectEventType::PaymentDisputeUpdated => match webhook.data { - rapyd::WebhookData::Dispute(data) => api::IncomingWebhookEvent::from(data.status), - _ => api::IncomingWebhookEvent::EventNotSupported, + rapyd::WebhookData::Dispute(data) => IncomingWebhookEvent::from(data.status), + _ => IncomingWebhookEvent::EventNotSupported, }, }) } fn get_webhook_resource_object( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { let webhook: transformers::RapydIncomingWebhook = request .body @@ -953,8 +925,8 @@ impl api::IncomingWebhook for Rapyd { fn get_dispute_details( &self, - request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { let webhook: transformers::RapydIncomingWebhook = request .body .parse_struct("RapydIncomingWebhook") @@ -963,9 +935,9 @@ impl api::IncomingWebhook for Rapyd { transformers::WebhookData::Dispute(dispute_data) => Ok(dispute_data), _ => Err(errors::ConnectorError::WebhookBodyDecodingFailed), }?; - Ok(api::disputes::DisputePayload { + Ok(DisputePayload { amount: webhook_dispute_data.amount.to_string(), - currency: webhook_dispute_data.currency.to_string(), + currency: webhook_dispute_data.currency, dispute_stage: api_models::enums::DisputeStage::Dispute, connector_dispute_id: webhook_dispute_data.token, connector_reason: Some(webhook_dispute_data.dispute_reason_description), @@ -977,3 +949,5 @@ impl api::IncomingWebhook for Rapyd { }) } } + +impl ConnectorSpecifications for Rapyd {} diff --git a/crates/router/src/connector/rapyd/transformers.rs b/crates/hyperswitch_connectors/src/connectors/rapyd/transformers.rs similarity index 78% rename from crates/router/src/connector/rapyd/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/rapyd/transformers.rs index 044a64b413cf..778fe31978c0 100644 --- a/crates/router/src/connector/rapyd/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/rapyd/transformers.rs @@ -1,42 +1,47 @@ +use common_enums::enums; +use common_utils::{ext_traits::OptionExt, request::Method, types::MinorUnit}; use error_stack::ResultExt; +use hyperswitch_domain_models::{ + payment_method_data::{PaymentMethodData, WalletData}, + router_data::{ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData}, + types, +}; +use hyperswitch_interfaces::{consts::NO_ERROR_CODE, errors}; +use masking::Secret; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; use url::Url; use crate::{ - connector::utils::{PaymentsAuthorizeRequestData, RouterData}, - consts, - core::errors, - pii::Secret, - services, - types::{self, api, domain, storage::enums, transformers::ForeignFrom}, - utils::OptionExt, + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::{PaymentsAuthorizeRequestData, RouterData as _}, }; #[derive(Debug, Serialize)] pub struct RapydRouterData { - pub amount: i64, + pub amount: MinorUnit, pub router_data: T, } -impl TryFrom<(&api::CurrencyUnit, enums::Currency, i64, T)> for RapydRouterData { - type Error = error_stack::Report; - fn try_from( - (_currency_unit, _currency, amount, item): (&api::CurrencyUnit, enums::Currency, i64, T), - ) -> Result { - Ok(Self { +impl From<(MinorUnit, T)> for RapydRouterData { + fn from((amount, router_data): (MinorUnit, T)) -> Self { + Self { amount, - router_data: item, - }) + router_data, + } } } #[derive(Default, Debug, Serialize)] pub struct RapydPaymentsRequest { - pub amount: i64, + pub amount: MinorUnit, pub currency: enums::Currency, pub payment_method: PaymentMethod, pub payment_method_options: Option, + pub merchant_reference_id: Option, pub capture: Option, pub description: Option, pub complete_payment_url: Option, @@ -93,7 +98,7 @@ impl TryFrom<&RapydRouterData<&types::PaymentsAuthorizeRouterData>> for RapydPay item: &RapydRouterData<&types::PaymentsAuthorizeRouterData>, ) -> Result { let (capture, payment_method_options) = match item.router_data.payment_method { - diesel_models::enums::PaymentMethod::Card => { + enums::PaymentMethod::Card => { let three_ds_enabled = matches!( item.router_data.auth_type, enums::AuthenticationType::ThreeDs @@ -104,7 +109,9 @@ impl TryFrom<&RapydRouterData<&types::PaymentsAuthorizeRouterData>> for RapydPay ( Some(matches!( item.router_data.request.capture_method, - Some(enums::CaptureMethod::Automatic) | None + Some(enums::CaptureMethod::Automatic) + | Some(enums::CaptureMethod::SequentialAutomatic) + | None )), Some(payment_method_options), ) @@ -112,7 +119,7 @@ impl TryFrom<&RapydRouterData<&types::PaymentsAuthorizeRouterData>> for RapydPay _ => (None, None), }; let payment_method = match item.router_data.request.payment_method_data { - domain::PaymentMethodData::Card(ref ccard) => { + PaymentMethodData::Card(ref ccard) => { Some(PaymentMethod { pm_type: "in_amex_card".to_owned(), //[#369] Map payment method type based on country fields: Some(PaymentFields { @@ -130,13 +137,13 @@ impl TryFrom<&RapydRouterData<&types::PaymentsAuthorizeRouterData>> for RapydPay digital_wallet: None, }) } - domain::PaymentMethodData::Wallet(ref wallet_data) => { + PaymentMethodData::Wallet(ref wallet_data) => { let digital_wallet = match wallet_data { - domain::WalletData::GooglePay(data) => Some(RapydWallet { + WalletData::GooglePay(data) => Some(RapydWallet { payment_type: "google_pay".to_string(), token: Some(Secret::new(data.tokenization_data.token.to_owned())), }), - domain::WalletData::ApplePay(data) => Some(RapydWallet { + WalletData::ApplePay(data) => Some(RapydWallet { payment_type: "apple_pay".to_string(), token: Some(Secret::new(data.payment_data.to_string())), }), @@ -155,13 +162,14 @@ impl TryFrom<&RapydRouterData<&types::PaymentsAuthorizeRouterData>> for RapydPay .change_context(errors::ConnectorError::NotImplemented( "payment_method".to_owned(), ))?; - let return_url = item.router_data.request.get_return_url()?; + let return_url = item.router_data.request.get_router_return_url()?; Ok(Self { amount: item.amount, currency: item.router_data.request.currency, payment_method, capture, payment_method_options, + merchant_reference_id: Some(item.router_data.connector_request_reference_id.clone()), description: None, error_payment_url: Some(return_url.clone()), complete_payment_url: Some(return_url), @@ -175,10 +183,10 @@ pub struct RapydAuthType { pub secret_key: Secret, } -impl TryFrom<&types::ConnectorAuthType> for RapydAuthType { +impl TryFrom<&ConnectorAuthType> for RapydAuthType { type Error = error_stack::Report; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { - if let types::ConnectorAuthType::BodyKey { api_key, key1 } = auth_type { + fn try_from(auth_type: &ConnectorAuthType) -> Result { + if let ConnectorAuthType::BodyKey { api_key, key1 } = auth_type { Ok(Self { access_key: api_key.to_owned(), secret_key: key1.to_owned(), @@ -209,28 +217,24 @@ pub enum RapydPaymentStatus { New, } -impl ForeignFrom<(RapydPaymentStatus, NextAction)> for enums::AttemptStatus { - fn foreign_from(item: (RapydPaymentStatus, NextAction)) -> Self { - let (status, next_action) = item; - match (status, next_action) { - (RapydPaymentStatus::Closed, _) => Self::Charged, - ( - RapydPaymentStatus::Active, - NextAction::ThreedsVerification | NextAction::PendingConfirmation, - ) => Self::AuthenticationPending, - ( - RapydPaymentStatus::Active, - NextAction::PendingCapture | NextAction::NotApplicable, - ) => Self::Authorized, - ( - RapydPaymentStatus::CanceledByClientOrBank - | RapydPaymentStatus::Expired - | RapydPaymentStatus::ReversedByRapyd, - _, - ) => Self::Voided, - (RapydPaymentStatus::Error, _) => Self::Failure, - (RapydPaymentStatus::New, _) => Self::Authorizing, +fn get_status(status: RapydPaymentStatus, next_action: NextAction) -> enums::AttemptStatus { + match (status, next_action) { + (RapydPaymentStatus::Closed, _) => enums::AttemptStatus::Charged, + ( + RapydPaymentStatus::Active, + NextAction::ThreedsVerification | NextAction::PendingConfirmation, + ) => enums::AttemptStatus::AuthenticationPending, + (RapydPaymentStatus::Active, NextAction::PendingCapture | NextAction::NotApplicable) => { + enums::AttemptStatus::Authorized } + ( + RapydPaymentStatus::CanceledByClientOrBank + | RapydPaymentStatus::Expired + | RapydPaymentStatus::ReversedByRapyd, + _, + ) => enums::AttemptStatus::Voided, + (RapydPaymentStatus::Error, _) => enums::AttemptStatus::Failure, + (RapydPaymentStatus::New, _) => enums::AttemptStatus::Authorizing, } } @@ -274,6 +278,7 @@ pub struct ResponseData { pub country_code: Option, pub captured: Option, pub transaction_id: String, + pub merchant_reference_id: Option, pub paid: Option, pub failure_code: Option, pub failure_message: Option, @@ -299,7 +304,7 @@ pub struct DisputeResponseData { #[derive(Default, Debug, Serialize)] pub struct RapydRefundRequest { pub payment: String, - pub amount: Option, + pub amount: Option, pub currency: Option, } @@ -356,12 +361,12 @@ pub struct RefundResponseData { pub failure_reason: Option, } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { let (connector_refund_id, refund_status) = match item.response.data { Some(data) => (data.id, enums::RefundStatus::from(data.status)), @@ -371,7 +376,7 @@ impl TryFrom> ), }; Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id, refund_status, }), @@ -380,12 +385,10 @@ impl TryFrom> } } -impl TryFrom> - for types::RefundsRouterData -{ +impl TryFrom> for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { let (connector_refund_id, refund_status) = match item.response.data { Some(data) => (data.id, enums::RefundStatus::from(data.status)), @@ -395,7 +398,7 @@ impl TryFrom> ), }; Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id, refund_status, }), @@ -406,7 +409,7 @@ impl TryFrom> #[derive(Debug, Serialize, Clone)] pub struct CaptureRequest { - amount: Option, + amount: Option, receipt_email: Option>, statement_descriptor: Option, } @@ -424,24 +427,21 @@ impl TryFrom<&RapydRouterData<&types::PaymentsCaptureRouterData>> for CaptureReq } } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { let (status, response) = match &item.response.data { Some(data) => { - let attempt_status = enums::AttemptStatus::foreign_from(( - data.status.to_owned(), - data.next_action.to_owned(), - )); + let attempt_status = + get_status(data.status.to_owned(), data.next_action.to_owned()); match attempt_status { - diesel_models::enums::AttemptStatus::Failure => ( + enums::AttemptStatus::Failure => ( enums::AttemptStatus::Failure, - Err(types::ErrorResponse { + Err(ErrorResponse { code: data .failure_code .to_owned() @@ -465,20 +465,20 @@ impl }) .transpose()?; - let redirection_data = redirection_url - .map(|url| services::RedirectForm::from((url, services::Method::Get))); + let redirection_data = + redirection_url.map(|url| RedirectForm::from((url, Method::Get))); ( attempt_status, - Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - data.id.to_owned(), - ), //transaction_id is also the field but this id is used to initiate a refund - redirection_data, - mandate_reference: None, + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(data.id.to_owned()), //transaction_id is also the field but this id is used to initiate a refund + redirection_data: Box::new(redirection_data), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: data + .merchant_reference_id + .to_owned(), incremental_authorization_allowed: None, charge_id: None, }), @@ -488,7 +488,7 @@ impl } None => ( enums::AttemptStatus::Failure, - Err(types::ErrorResponse { + Err(ErrorResponse { code: item.response.status.error_code, status_code: item.http_code, message: item.response.status.status.unwrap_or_default(), @@ -571,7 +571,7 @@ impl From for RapydPaymentsResponse { fn from(value: ResponseData) -> Self { Self { status: Status { - error_code: consts::NO_ERROR_CODE.to_owned(), + error_code: NO_ERROR_CODE.to_owned(), status: None, message: None, response_code: None, @@ -586,7 +586,7 @@ impl From for RefundResponse { fn from(value: RefundResponseData) -> Self { Self { status: Status { - error_code: consts::NO_ERROR_CODE.to_owned(), + error_code: NO_ERROR_CODE.to_owned(), status: None, message: None, response_code: None, diff --git a/crates/router/src/connector/razorpay.rs b/crates/hyperswitch_connectors/src/connectors/razorpay.rs similarity index 70% rename from crates/router/src/connector/razorpay.rs rename to crates/hyperswitch_connectors/src/connectors/razorpay.rs index 3dcb636b42a9..f5f3aa85e897 100644 --- a/crates/router/src/connector/razorpay.rs +++ b/crates/hyperswitch_connectors/src/connectors/razorpay.rs @@ -1,31 +1,51 @@ pub mod transformers; +use api_models::webhooks::{self, IncomingWebhookEvent}; use common_utils::{ - ext_traits::ByteSliceExt, + errors::CustomResult, + ext_traits::{ByteSliceExt, BytesExt}, + request::{Method, Request, RequestBuilder, RequestContent}, types::{AmountConvertor, FloatMajorUnit, FloatMajorUnitForConnector}, }; use error_stack::{Report, ResultExt}; -use masking::ExposeInterface; -use transformers as razorpay; - -use super::utils::{self as connector_utils}; -use crate::{ - configs::settings, - consts, - core::errors::{self, CustomResult}, - events::connector_api_logs::ConnectorEvent, - headers, logger, - services::{ - self, - request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, +use hyperswitch_domain_models::{ + router_data::{AccessToken, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, types::{ - self, - api::{self, ConnectorCommon, ConnectorCommonExt}, - ErrorResponse, RequestContent, Response, + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, + RefundSyncRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, }, - utils::BytesExt, + configs::Connectors, + consts::NO_ERROR_CODE, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, +}; +use masking::{ExposeInterface, Mask}; +use router_env::logger; +use transformers as razorpay; + +use crate::{ + constants::headers, + types::ResponseRouterData, + utils::{convert_amount, handle_json_response_deserialization_failure}, }; #[derive(Clone)] @@ -54,12 +74,8 @@ impl api::RefundExecute for Razorpay {} impl api::RefundSync for Razorpay {} impl api::PaymentToken for Razorpay {} -impl - ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Razorpay +impl ConnectorIntegration + for Razorpay { // Not Implemented (R) } @@ -70,9 +86,9 @@ where { fn build_headers( &self, - _req: &types::RouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + _req: &RouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let header = vec![ ( headers::CONTENT_TYPE.to_string(), @@ -100,7 +116,7 @@ impl ConnectorCommon for Razorpay { "application/json" } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.razorpay.base_url.as_ref() } @@ -144,7 +160,7 @@ impl ConnectorCommon for Razorpay { razorpay::ErrorResponse::RazorpayStringError(error_string) => { Ok(ErrorResponse { status_code: res.status_code, - code: consts::NO_ERROR_CODE.to_string(), + code: NO_ERROR_CODE.to_string(), message: error_string.clone(), reason: Some(error_string.clone()), attempt_status: None, @@ -156,7 +172,7 @@ impl ConnectorCommon for Razorpay { Err(error_msg) => { event_builder.map(|event| event.set_error(serde_json::json!({"error": res.response.escape_ascii().to_string(), "status_code": res.status_code}))); logger::error!(deserialization_error =? error_msg); - crate::utils::handle_json_response_deserialization_failure(res, "razorpay") + handle_json_response_deserialization_failure(res, "razorpay") } } } @@ -166,34 +182,23 @@ impl ConnectorValidation for Razorpay { //TODO: implement functions when support enabled } -impl ConnectorIntegration - for Razorpay -{ +impl ConnectorIntegration for Razorpay { //TODO: implement sessions flow } -impl ConnectorIntegration - for Razorpay -{ -} +impl ConnectorIntegration for Razorpay {} -impl - ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Razorpay +impl ConnectorIntegration + for Razorpay { } -impl ConnectorIntegration - for Razorpay -{ +impl ConnectorIntegration for Razorpay { fn get_headers( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -203,8 +208,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}gatewayProxy/txn/sendCollect", @@ -214,10 +219,10 @@ impl ConnectorIntegration CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_amount, req.request.currency, @@ -230,12 +235,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsAuthorizeType::get_url( self, req, connectors, )?) @@ -252,17 +257,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: razorpay::RazorpayPaymentsResponse = res .response .parse_struct("Razorpay PaymentsAuthorizeResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -278,14 +283,12 @@ impl ConnectorIntegration - for Razorpay -{ +impl ConnectorIntegration for Razorpay { fn get_headers( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -295,8 +298,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}gatewayProxy/sync/transaction", @@ -306,10 +309,10 @@ impl ConnectorIntegration CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.amount, req.request.currency, @@ -322,11 +325,11 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) @@ -339,17 +342,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: razorpay::RazorpaySyncResponse = res .response .parse_struct("razorpay PaymentsSyncResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -365,14 +368,12 @@ impl ConnectorIntegration - for Razorpay -{ +impl ConnectorIntegration for Razorpay { fn get_headers( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -382,28 +383,28 @@ impl ConnectorIntegration CustomResult { Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) } fn get_request_body( &self, - _req: &types::PaymentsCaptureRouterData, - _connectors: &settings::Connectors, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, ) -> CustomResult { Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) } fn build_request( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsCaptureType::get_headers( @@ -418,17 +419,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: razorpay::RazorpayPaymentsResponse = res .response .parse_struct("Razorpay PaymentsCaptureResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -444,19 +445,14 @@ impl ConnectorIntegration - for Razorpay -{ -} +impl ConnectorIntegration for Razorpay {} -impl ConnectorIntegration - for Razorpay -{ +impl ConnectorIntegration for Razorpay { fn get_headers( &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -466,18 +462,18 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, + _req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!("{}gatewayProxy/refund", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, + req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_refund_amount, req.request.currency, @@ -490,11 +486,11 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) .url(&types::RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundExecuteType::get_headers( @@ -509,17 +505,17 @@ impl ConnectorIntegration, + data: &RefundsRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult, errors::ConnectorError> { let response: razorpay::RefundResponse = res .response .parse_struct("razorpay RefundResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -535,12 +531,12 @@ impl ConnectorIntegration for Razorpay { +impl ConnectorIntegration for Razorpay { fn get_headers( &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -550,8 +546,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}gatewayProxy/sync/refund", @@ -561,10 +557,10 @@ impl ConnectorIntegration CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_refund_amount, req.request.currency, @@ -577,12 +573,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundSyncType::get_headers(self, req, connectors)?) @@ -595,17 +591,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: razorpay::RefundResponse = res .response .parse_struct("razorpay RefundSyncResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -622,17 +618,17 @@ impl ConnectorIntegration, - ) -> CustomResult { + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { let webhook_resource_object = get_webhook_object_from_body(request.body)?; match webhook_resource_object.refund { - Some(refund_data) => Ok(api_models::webhooks::ObjectReferenceId::RefundId( - api_models::webhooks::RefundIdType::ConnectorRefundId(refund_data.entity.id), + Some(refund_data) => Ok(webhooks::ObjectReferenceId::RefundId( + webhooks::RefundIdType::ConnectorRefundId(refund_data.entity.id), )), - None => Ok(api_models::webhooks::ObjectReferenceId::PaymentId( + None => Ok(webhooks::ObjectReferenceId::PaymentId( api_models::payments::PaymentIdType::ConnectorTransactionId( webhook_resource_object.payment.entity.id, ), @@ -642,7 +638,7 @@ impl api::IncomingWebhook for Razorpay { async fn verify_webhook_source( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &IncomingWebhookRequestDetails<'_>, _merchant_id: &common_utils::id_type::MerchantId, _connector_webhook_details: Option, _connector_account_details: common_utils::crypto::Encryptable< @@ -655,17 +651,15 @@ impl api::IncomingWebhook for Razorpay { fn get_webhook_event_type( &self, - request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { let webhook_resource_object = get_webhook_object_from_body(request.body)?; - Ok(api_models::webhooks::IncomingWebhookEvent::try_from( - webhook_resource_object, - )?) + Ok(IncomingWebhookEvent::try_from(webhook_resource_object)?) } fn get_webhook_resource_object( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { let webhook_resource_object = get_webhook_object_from_body(request.body)?; Ok(Box::new(webhook_resource_object)) @@ -681,3 +675,5 @@ fn get_webhook_object_from_body( Ok(details.payload) } + +impl ConnectorSpecifications for Razorpay {} diff --git a/crates/router/src/connector/razorpay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/razorpay/transformers.rs similarity index 90% rename from crates/router/src/connector/razorpay/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/razorpay/transformers.rs index 621c0969aff0..c799cf2594ee 100644 --- a/crates/router/src/connector/razorpay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/razorpay/transformers.rs @@ -1,20 +1,28 @@ +use common_enums::enums; use common_utils::{ pii::{self, Email}, types::FloatMajorUnit, }; use error_stack::ResultExt; -use hyperswitch_domain_models::payment_method_data::UpiCollectData; +use hyperswitch_domain_models::{ + payment_method_data::{PaymentMethodData, UpiCollectData, UpiData}, + router_data::{ConnectorAuthType, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types, +}; +use hyperswitch_interfaces::{ + configs::Connectors, + consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE}, + errors, +}; use masking::Secret; use rand::Rng; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; -use crate::{ - configs::settings, - consts, - core::errors, - types::{self, api, domain, storage::enums}, -}; +use crate::types::{RefundsResponseRouterData, ResponseRouterData}; pub struct RazorpayRouterData { pub amount: FloatMajorUnit, @@ -50,7 +58,6 @@ pub struct RazorpayPaymentsRequest { #[derive(Default, Debug, Serialize, Eq, PartialEq)] #[serde(rename_all = "camelCase")] - pub struct SecondFactor { txn_id: String, id: String, @@ -367,40 +374,43 @@ fn generate_12_digit_number() -> u64 { impl TryFrom<( &RazorpayRouterData<&types::PaymentsAuthorizeRouterData>, - &settings::Connectors, + &Connectors, )> for RazorpayPaymentsRequest { type Error = error_stack::Report; fn try_from( (item, data): ( &RazorpayRouterData<&types::PaymentsAuthorizeRouterData>, - &settings::Connectors, + &Connectors, ), ) -> Result { let request = &item.router_data.request; let txn_card_info = match request.payment_method_data.clone() { - domain::PaymentMethodData::Upi(upi_type) => match upi_type { - domain::UpiData::UpiCollect(upi_data) => TxnCardInfo::try_from((item, upi_data)), - hyperswitch_domain_models::payment_method_data::UpiData::UpiIntent(_) => Err( - errors::ConnectorError::NotImplemented("Payment methods".to_string()).into(), - ), + PaymentMethodData::Upi(upi_type) => match upi_type { + UpiData::UpiCollect(upi_data) => TxnCardInfo::try_from((item, upi_data)), + UpiData::UpiIntent(_) => Err(errors::ConnectorError::NotImplemented( + "Payment methods".to_string(), + ) + .into()), }, - domain::PaymentMethodData::Card(_) - | 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::RealTimePayment(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + PaymentMethodData::Card(_) + | PaymentMethodData::CardRedirect(_) + | PaymentMethodData::Wallet(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()) } }?; @@ -456,7 +466,7 @@ impl TryFrom<&RazorpayRouterData<&types::PaymentsAuthorizeRouterData>> for Secon impl TryFrom<( &RazorpayRouterData<&types::PaymentsAuthorizeRouterData>, - &settings::Connectors, + &Connectors, )> for MerchantAccount { type Error = error_stack::Report; @@ -464,7 +474,7 @@ impl fn try_from( (_item, data): ( &RazorpayRouterData<&types::PaymentsAuthorizeRouterData>, - &settings::Connectors, + &Connectors, ), ) -> Result { let merchant_data = JuspayAuthData::try_from(data)?; @@ -482,7 +492,7 @@ impl impl TryFrom<( &RazorpayRouterData<&types::PaymentsAuthorizeRouterData>, - &settings::Connectors, + &Connectors, )> for OrderReference { type Error = error_stack::Report; @@ -490,7 +500,7 @@ impl fn try_from( (item, data): ( &RazorpayRouterData<&types::PaymentsAuthorizeRouterData>, - &settings::Connectors, + &Connectors, ), ) -> Result { let ref_id = generate_12_digit_number(); @@ -528,7 +538,7 @@ impl let item = payment_data.0; let upi_data = payment_data.1; let ref_id = generate_12_digit_number(); - let pm = common_enums::enums::PaymentMethod::Upi; + let pm = enums::PaymentMethod::Upi; Ok(Self { txn_detail_id: ref_id.to_string(), txn_id: item.router_data.connector_request_reference_id.clone(), @@ -549,7 +559,7 @@ impl impl TryFrom<( &RazorpayRouterData<&types::PaymentsAuthorizeRouterData>, - &settings::Connectors, + &Connectors, )> for TxnDetail { type Error = error_stack::Report; @@ -557,7 +567,7 @@ impl fn try_from( (item, data): ( &RazorpayRouterData<&types::PaymentsAuthorizeRouterData>, - &settings::Connectors, + &Connectors, ), ) -> Result { let ref_id = generate_12_digit_number(); @@ -596,7 +606,7 @@ impl impl TryFrom<( &RazorpayRouterData<&types::PaymentsAuthorizeRouterData>, - &settings::Connectors, + &Connectors, )> for MerchantGatewayAccount { type Error = error_stack::Report; @@ -604,7 +614,7 @@ impl fn try_from( (item, data): ( &RazorpayRouterData<&types::PaymentsAuthorizeRouterData>, - &settings::Connectors, + &Connectors, ), ) -> Result { let ref_id = generate_12_digit_number(); @@ -648,11 +658,11 @@ pub struct RazorpayAuthType { pub(super) razorpay_secret: Secret, } -impl TryFrom<&types::ConnectorAuthType> for RazorpayAuthType { +impl TryFrom<&ConnectorAuthType> for RazorpayAuthType { type Error = error_stack::Report; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { + fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - types::ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { + ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { razorpay_id: api_key.to_owned(), razorpay_secret: key1.to_owned(), }), @@ -664,11 +674,11 @@ impl TryFrom<&types::ConnectorAuthType> for RazorpayAuthType { pub struct JuspayAuthData { pub(super) merchant_id: Secret, } -impl TryFrom<&settings::Connectors> for JuspayAuthData { +impl TryFrom<&Connectors> for JuspayAuthData { type Error = error_stack::Report; - fn try_from(connector_param: &settings::Connectors) -> Result { - let settings::Connectors { razorpay, .. } = connector_param; + fn try_from(connector_param: &Connectors) -> Result { + let Connectors { razorpay, .. } = connector_param; Ok(Self { merchant_id: razorpay.merchant_id.clone(), }) @@ -766,30 +776,22 @@ impl From for TxnStatus { } } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< - F, - RazorpayPaymentsResponse, - T, - types::PaymentsResponseData, - >, + item: ResponseRouterData, ) -> Result { let second_factor = item.response.contents.second_factor; let status = enums::AttemptStatus::from(item.response.contents.txn_status); match second_factor { Some(second_factor) => Ok(Self { status, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - second_factor.epg_txn_id, - ), - redirection_data: None, - mandate_reference: None, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(second_factor.epg_txn_id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(second_factor.txn_id), @@ -805,10 +807,10 @@ impl .pgr_info .resp_message .clone() - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()); + .unwrap_or(NO_ERROR_MESSAGE.to_string()); Ok(Self { status, - response: Err(types::ErrorResponse { + response: Err(hyperswitch_domain_models::router_data::ErrorResponse { code: item.response.contents.pgr_info.resp_code.clone(), message: message_code.clone(), reason: Some(message_code.clone()), @@ -858,7 +860,7 @@ pub struct AccountDetails { impl TryFrom<( RazorpayRouterData<&types::PaymentsSyncRouterData>, - &settings::Connectors, + &Connectors, )> for RazorpayCreateSyncRequest { type Error = error_stack::Report; @@ -866,7 +868,7 @@ impl fn try_from( (item, data): ( RazorpayRouterData<&types::PaymentsSyncRouterData>, - &settings::Connectors, + &Connectors, ), ) -> Result { let ref_id = generate_12_digit_number(); @@ -978,7 +980,6 @@ pub struct RazorpaySyncResponse { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] - pub enum PsyncStatus { Charged, Pending, @@ -995,22 +996,21 @@ impl From for enums::AttemptStatus { } } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { Ok(Self { status: enums::AttemptStatus::from(item.response.status), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( item.response.second_factor.epg_txn_id, ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(item.response.second_factor.txn_id), @@ -1052,7 +1052,6 @@ pub struct Refund { #[derive(Default, Debug, Serialize, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] - pub enum RefundStatus { Success, Failure, @@ -1070,14 +1069,14 @@ pub struct PaymentGatewayResponse { impl TryFrom<( &RazorpayRouterData<&types::RefundsRouterData>, - &settings::Connectors, + &Connectors, )> for RazorpayRefundRequest { type Error = error_stack::Report; fn try_from( (item, data): ( &RazorpayRouterData<&types::RefundsRouterData>, - &settings::Connectors, + &Connectors, ), ) -> Result { let ref_id = generate_12_digit_number(); @@ -1165,7 +1164,7 @@ impl let payment_source: Secret = Secret::new("".to_string()); - let pm = common_enums::enums::PaymentMethod::Upi; + let pm = enums::PaymentMethod::Upi; let txn_card_info = TxnCardInfo { txn_detail_id: ref_id.to_string(), @@ -1219,7 +1218,6 @@ impl From for enums::RefundStatus { #[derive(Default, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] - pub struct RefundResponse { txn_id: Option, refund: RefundRes, @@ -1243,34 +1241,34 @@ pub struct RefundRes { date_created: Option, } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { let epg_txn_id = item.response.refund.epg_txn_id.clone(); let refund_status = enums::RefundStatus::from(item.response.refund.status); let response = match epg_txn_id { - Some(epg_txn_id) => Ok(types::RefundsResponseData { + Some(epg_txn_id) => Ok(RefundsResponseData { connector_refund_id: epg_txn_id, refund_status, }), - None => Err(types::ErrorResponse { + None => Err(hyperswitch_domain_models::router_data::ErrorResponse { code: item .response .refund .error_message .clone() - .unwrap_or(consts::NO_ERROR_CODE.to_string()), + .unwrap_or(NO_ERROR_CODE.to_string()), message: item .response .refund .response_code .clone() - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + .unwrap_or(NO_ERROR_MESSAGE.to_string()), reason: item.response.refund.response_code.clone(), status_code: item.http_code, attempt_status: None, @@ -1284,15 +1282,13 @@ impl TryFrom> } } -impl TryFrom> - for types::RefundsRouterData -{ +impl TryFrom> for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item .data .request diff --git a/crates/hyperswitch_connectors/src/connectors/redsys.rs b/crates/hyperswitch_connectors/src/connectors/redsys.rs new file mode 100644 index 000000000000..501182dec07a --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/redsys.rs @@ -0,0 +1,568 @@ +pub mod transformers; + +use common_utils::{ + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, +}; +use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::{ + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, + RefundSyncRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks, +}; +use masking::{ExposeInterface, Mask}; +use transformers as redsys; + +use crate::{constants::headers, types::ResponseRouterData, utils}; + +#[derive(Clone)] +pub struct Redsys { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl Redsys { + pub fn new() -> &'static Self { + &Self { + amount_converter: &StringMinorUnitForConnector, + } + } +} + +impl api::Payment for Redsys {} +impl api::PaymentSession for Redsys {} +impl api::ConnectorAccessToken for Redsys {} +impl api::MandateSetup for Redsys {} +impl api::PaymentAuthorize for Redsys {} +impl api::PaymentSync for Redsys {} +impl api::PaymentCapture for Redsys {} +impl api::PaymentVoid for Redsys {} +impl api::Refund for Redsys {} +impl api::RefundExecute for Redsys {} +impl api::RefundSync for Redsys {} +impl api::PaymentToken for Redsys {} + +impl ConnectorIntegration + for Redsys +{ + // Not Implemented (R) +} + +impl ConnectorCommonExt for Redsys +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } +} + +impl ConnectorCommon for Redsys { + fn id(&self) -> &'static str { + "redsys" + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Base + // TODO! Check connector documentation, on which unit they are processing the currency. + // If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor, + // if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { + connectors.redsys.base_url.as_ref() + } + + fn get_auth_header( + &self, + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + let auth = redsys::RedsysAuthType::try_from(auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + Ok(vec![( + headers::AUTHORIZATION.to_string(), + auth.api_key.expose().into_masked(), + )]) + } + + fn build_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: redsys::RedsysErrorResponse = res + .response + .parse_struct("RedsysErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.code, + message: response.message, + reason: response.reason, + attempt_status: None, + connector_transaction_id: None, + }) + } +} + +impl ConnectorValidation for Redsys { + //TODO: implement functions when support enabled +} + +impl ConnectorIntegration for Redsys { + //TODO: implement sessions flow +} + +impl ConnectorIntegration for Redsys {} + +impl ConnectorIntegration for Redsys {} + +impl ConnectorIntegration for Redsys { + fn get_headers( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + + let connector_router_data = redsys::RedsysRouterData::from((amount, req)); + let connector_req = redsys::RedsysPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: redsys::RedsysPaymentsResponse = res + .response + .parse_struct("Redsys PaymentsAuthorizeResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Redsys { + fn get_headers( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: redsys::RedsysPaymentsResponse = res + .response + .parse_struct("redsys PaymentsSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Redsys { + fn get_headers( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsCaptureType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCaptureType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCaptureRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: redsys::RedsysPaymentsResponse = res + .response + .parse_struct("Redsys PaymentsCaptureResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Redsys {} + +impl ConnectorIntegration for Redsys { + fn get_headers( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let refund_amount = utils::convert_amount( + self.amount_converter, + req.request.minor_refund_amount, + req.request.currency, + )?; + + let connector_router_data = redsys::RedsysRouterData::from((refund_amount, req)); + let connector_req = redsys::RedsysRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundExecuteType::get_headers( + self, req, connectors, + )?) + .set_body(types::RefundExecuteType::get_request_body( + self, req, connectors, + )?) + .build(); + Ok(Some(request)) + } + + fn handle_response( + &self, + data: &RefundsRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult, errors::ConnectorError> { + let response: redsys::RefundResponse = + res.response + .parse_struct("redsys RefundResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Redsys { + fn get_headers( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &RefundSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::RefundSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundSyncType::get_headers(self, req, connectors)?) + .set_body(types::RefundSyncType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &RefundSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: redsys::RefundResponse = res + .response + .parse_struct("redsys RefundSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +#[async_trait::async_trait] +impl webhooks::IncomingWebhook for Redsys { + fn get_webhook_object_reference_id( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_event_type( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_resource_object( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } +} + +impl ConnectorSpecifications for Redsys {} diff --git a/crates/hyperswitch_connectors/src/connectors/redsys/transformers.rs b/crates/hyperswitch_connectors/src/connectors/redsys/transformers.rs new file mode 100644 index 000000000000..78329b5719d2 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/redsys/transformers.rs @@ -0,0 +1,228 @@ +use common_enums::enums; +use common_utils::types::StringMinorUnit; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{ConnectorAuthType, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{PaymentsAuthorizeRouterData, RefundsRouterData}, +}; +use hyperswitch_interfaces::errors; +use masking::Secret; +use serde::{Deserialize, Serialize}; + +use crate::{ + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::PaymentsAuthorizeRequestData, +}; + +//TODO: Fill the struct with respective fields +pub struct RedsysRouterData { + pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub router_data: T, +} + +impl From<(StringMinorUnit, T)> for RedsysRouterData { + fn from((amount, item): (StringMinorUnit, T)) -> Self { + //Todo : use utils to convert the amount to the type of amount that a connector accepts + Self { + amount, + router_data: item, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, PartialEq)] +pub struct RedsysPaymentsRequest { + amount: StringMinorUnit, + card: RedsysCard, +} + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct RedsysCard { + number: cards::CardNumber, + expiry_month: Secret, + expiry_year: Secret, + cvc: Secret, + complete: bool, +} + +impl TryFrom<&RedsysRouterData<&PaymentsAuthorizeRouterData>> for RedsysPaymentsRequest { + type Error = error_stack::Report; + fn try_from( + item: &RedsysRouterData<&PaymentsAuthorizeRouterData>, + ) -> Result { + match item.router_data.request.payment_method_data.clone() { + PaymentMethodData::Card(req_card) => { + let card = RedsysCard { + number: req_card.card_number, + expiry_month: req_card.card_exp_month, + expiry_year: req_card.card_exp_year, + cvc: req_card.card_cvc, + complete: item.router_data.request.is_auto_capture()?, + }; + Ok(Self { + amount: item.amount.clone(), + card, + }) + } + _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), + } + } +} + +//TODO: Fill the struct with respective fields +// Auth Struct +pub struct RedsysAuthType { + pub(super) api_key: Secret, +} + +impl TryFrom<&ConnectorAuthType> for RedsysAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &ConnectorAuthType) -> Result { + match auth_type { + ConnectorAuthType::HeaderKey { api_key } => Ok(Self { + api_key: api_key.to_owned(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} +// PaymentsResponse +//TODO: Append the remaining status flags +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum RedsysPaymentStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for common_enums::AttemptStatus { + fn from(item: RedsysPaymentStatus) -> Self { + match item { + RedsysPaymentStatus::Succeeded => Self::Charged, + RedsysPaymentStatus::Failed => Self::Failure, + RedsysPaymentStatus::Processing => Self::Authorizing, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct RedsysPaymentsResponse { + status: RedsysPaymentStatus, + id: String, +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + Ok(Self { + status: common_enums::AttemptStatus::from(item.response.status), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +// REFUND : +// Type definition for RefundRequest +#[derive(Default, Debug, Serialize)] +pub struct RedsysRefundRequest { + pub amount: StringMinorUnit, +} + +impl TryFrom<&RedsysRouterData<&RefundsRouterData>> for RedsysRefundRequest { + type Error = error_stack::Report; + fn try_from(item: &RedsysRouterData<&RefundsRouterData>) -> Result { + Ok(Self { + amount: item.amount.to_owned(), + }) + } +} + +// Type definition for Refund Response + +#[allow(dead_code)] +#[derive(Debug, Serialize, Default, Deserialize, Clone)] +pub enum RefundStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for enums::RefundStatus { + fn from(item: RefundStatus) -> Self { + match item { + RefundStatus::Succeeded => Self::Success, + RefundStatus::Failed => Self::Failure, + RefundStatus::Processing => Self::Pending, + //TODO: Review mapping + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct RefundResponse { + id: String, + status: RefundStatus, +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct RedsysErrorResponse { + pub status_code: u16, + pub code: String, + pub message: String, + pub reason: Option, +} diff --git a/crates/router/src/connector/shift4.rs b/crates/hyperswitch_connectors/src/connectors/shift4.rs similarity index 62% rename from crates/router/src/connector/shift4.rs rename to crates/hyperswitch_connectors/src/connectors/shift4.rs index 895487db3ba8..019f681ec5f5 100644 --- a/crates/router/src/connector/shift4.rs +++ b/crates/hyperswitch_connectors/src/connectors/shift4.rs @@ -1,34 +1,68 @@ pub mod transformers; -use std::fmt::Debug; - -use common_utils::{ext_traits::ByteSliceExt, request::RequestContent}; -use diesel_models::enums; +use api_models::webhooks::IncomingWebhookEvent; +use common_enums::enums; +use common_utils::{ + errors::CustomResult, + ext_traits::{ByteSliceExt, BytesExt}, + request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, MinorUnit, MinorUnitForConnector}, +}; use error_stack::{report, ResultExt}; -use transformers as shift4; - -use super::utils::{self as connector_utils, RefundsRequestData}; -use crate::{ - configs::settings, - consts, - core::errors::{self, CustomResult}, - events::connector_api_logs::ConnectorEvent, - headers, - services::{ - self, - request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, +use http::header::ACCEPT; +use hyperswitch_domain_models::{ + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + CompleteAuthorize, PreProcessing, }, + router_request_types::{ + AccessTokenRequestData, CompleteAuthorizeData, PaymentMethodTokenizationData, + PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsPreProcessingData, + PaymentsSessionData, PaymentsSyncData, RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, types::{ - self, - api::{self, ConnectorCommon, ConnectorCommonExt}, - ErrorResponse, + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, + PaymentsCompleteAuthorizeRouterData, PaymentsPreProcessingRouterData, + PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, }, - utils::BytesExt, + configs::Connectors, + consts::NO_ERROR_CODE, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, }; +use masking::Mask; +use transformers::{self as shift4, Shift4PaymentsRequest, Shift4RefundRequest}; -#[derive(Debug, Clone)] -pub struct Shift4; +use crate::{ + constants::headers, + types::ResponseRouterData, + utils::{construct_not_supported_error_report, convert_amount, RefundsRequestData}, +}; + +#[derive(Clone)] +pub struct Shift4 { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl Shift4 { + pub fn new() -> &'static Self { + &Self { + amount_converter: &MinorUnitForConnector, + } + } +} impl ConnectorCommonExt for Shift4 where @@ -36,16 +70,16 @@ where { fn build_headers( &self, - req: &types::RouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let mut headers = vec![ ( headers::CONTENT_TYPE.to_string(), self.get_content_type().to_string().into(), ), ( - headers::ACCEPT.to_string(), + ACCEPT.to_string(), self.get_content_type().to_string().into(), ), ]; @@ -63,14 +97,14 @@ impl ConnectorCommon for Shift4 { "application/json" } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.shift4.base_url.as_ref() } fn get_auth_header( &self, - auth_type: &types::ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { let auth = shift4::Shift4AuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; Ok(vec![( @@ -81,7 +115,7 @@ impl ConnectorCommon for Shift4 { fn build_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { let response: shift4::ErrorResponse = res @@ -97,7 +131,7 @@ impl ConnectorCommon for Shift4 { code: response .error .code - .unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()), + .unwrap_or_else(|| NO_ERROR_CODE.to_string()), message: response.error.message, reason: None, attempt_status: None, @@ -107,16 +141,19 @@ impl ConnectorCommon for Shift4 { } impl ConnectorValidation for Shift4 { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( - connector_utils::construct_not_supported_error_report(capture_method, self.id()), + construct_not_supported_error_report(capture_method, self.id()), ), } } @@ -135,41 +172,25 @@ impl api::RefundSync for Shift4 {} impl api::PaymentToken for Shift4 {} impl api::PaymentsPreProcessing for Shift4 {} -impl - ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Shift4 +impl ConnectorIntegration + for Shift4 { // Not Implemented (R) } impl api::ConnectorAccessToken for Shift4 {} -impl ConnectorIntegration - for Shift4 -{ +impl ConnectorIntegration for Shift4 { // Not Implemented (R) } impl api::MandateSetup for Shift4 {} -impl - ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Shift4 -{ +impl ConnectorIntegration for Shift4 { fn build_request( &self, - _req: &types::RouterData< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - >, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err( errors::ConnectorError::NotImplemented("Setup Mandate flow for Shift4".to_string()) .into(), @@ -178,14 +199,12 @@ impl } #[async_trait::async_trait] -impl ConnectorIntegration - for Shift4 -{ +impl ConnectorIntegration for Shift4 { fn get_headers( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -195,29 +214,35 @@ impl ConnectorIntegration CustomResult { Ok(format!("{}charges", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::PaymentsAuthorizeRouterData, - _connectors: &settings::Connectors, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, ) -> CustomResult { - let connector_req = shift4::Shift4PaymentsRequest::try_from(req)?; + let amount = convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + let connector_router_data = shift4::Shift4RouterData::try_from((amount, req))?; + let connector_req = Shift4PaymentsRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsAuthorizeType::get_url( self, req, connectors, )?) @@ -232,10 +257,10 @@ impl ConnectorIntegration, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: shift4::Shift4NonThreeDsResponse = res .response .parse_struct("Shift4NonThreeDsResponse") @@ -244,7 +269,7 @@ impl ConnectorIntegration, ) -> CustomResult { self.build_error_response(res, event_builder) } } -impl ConnectorIntegration - for Shift4 -{ -} +impl ConnectorIntegration for Shift4 {} -impl ConnectorIntegration - for Shift4 -{ +impl ConnectorIntegration for Shift4 { fn get_headers( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -283,8 +303,8 @@ impl ConnectorIntegration CustomResult { let connector_payment_id = req .request @@ -300,12 +320,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) + RequestBuilder::new() + .method(Method::Get) .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) @@ -315,7 +335,7 @@ impl ConnectorIntegration, ) -> CustomResult { self.build_error_response(res, event_builder) @@ -323,10 +343,10 @@ impl ConnectorIntegration, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: shift4::Shift4NonThreeDsResponse = res .response .parse_struct("Shift4NonThreeDsResponse") @@ -335,7 +355,7 @@ impl ConnectorIntegration - for Shift4 -{ +impl ConnectorIntegration for Shift4 { fn get_headers( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -361,12 +379,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsCaptureType::get_headers( @@ -378,10 +396,10 @@ impl ConnectorIntegration, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: shift4::Shift4NonThreeDsResponse = res .response .parse_struct("Shift4NonThreeDsResponse") @@ -390,7 +408,7 @@ impl ConnectorIntegration CustomResult { let connector_payment_id = req.request.connector_transaction_id.clone(); Ok(format!( @@ -413,38 +431,32 @@ impl ConnectorIntegration, ) -> CustomResult { self.build_error_response(res, event_builder) } } -impl ConnectorIntegration - for Shift4 -{ +impl ConnectorIntegration for Shift4 { //TODO: implement sessions flow } -impl - ConnectorIntegration< - api::PreProcessing, - types::PaymentsPreProcessingData, - types::PaymentsResponseData, - > for Shift4 +impl ConnectorIntegration + for Shift4 { fn get_headers( &self, - req: &types::PaymentsPreProcessingRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsPreProcessingRouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let mut headers = vec![ ( headers::CONTENT_TYPE.to_string(), "application/x-www-form-urlencoded".to_string().into(), ), ( - headers::ACCEPT.to_string(), + ACCEPT.to_string(), self.common_get_content_type().to_string().into(), ), ]; @@ -459,29 +471,43 @@ impl fn get_url( &self, - _req: &types::PaymentsPreProcessingRouterData, - connectors: &settings::Connectors, + _req: &PaymentsPreProcessingRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!("{}3d-secure", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::PaymentsPreProcessingRouterData, - _connectors: &settings::Connectors, + req: &PaymentsPreProcessingRouterData, + _connectors: &Connectors, ) -> CustomResult { - let connector_req = shift4::Shift4PaymentsRequest::try_from(req)?; + let amount = convert_amount( + self.amount_converter, + req.request.minor_amount.ok_or_else(|| { + errors::ConnectorError::MissingRequiredField { + field_name: "minor_amount", + } + })?, + req.request + .currency + .ok_or_else(|| errors::ConnectorError::MissingRequiredField { + field_name: "currency", + })?, + )?; + let connector_router_data = shift4::Shift4RouterData::try_from((amount, req))?; + let connector_req = Shift4PaymentsRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, - req: &types::PaymentsPreProcessingRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsPreProcessingRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsPreProcessingType::get_url( self, req, connectors, )?) @@ -498,10 +524,10 @@ impl fn handle_response( &self, - data: &types::PaymentsPreProcessingRouterData, + data: &PaymentsPreProcessingRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: shift4::Shift4ThreeDsResponse = res .response .parse_struct("Shift4ThreeDsResponse") @@ -510,7 +536,7 @@ impl event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -520,25 +546,21 @@ impl fn get_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { self.build_error_response(res, event_builder) } } -impl - ConnectorIntegration< - api::CompleteAuthorize, - types::CompleteAuthorizeData, - types::PaymentsResponseData, - > for Shift4 +impl ConnectorIntegration + for Shift4 { fn get_headers( &self, - req: &types::PaymentsCompleteAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -548,29 +570,35 @@ impl fn get_url( &self, - _req: &types::PaymentsCompleteAuthorizeRouterData, - connectors: &settings::Connectors, + _req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!("{}charges", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::PaymentsCompleteAuthorizeRouterData, - _connectors: &settings::Connectors, + req: &PaymentsCompleteAuthorizeRouterData, + _connectors: &Connectors, ) -> CustomResult { - let connector_req = shift4::Shift4PaymentsRequest::try_from(req)?; + let amount = convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + let connector_router_data = shift4::Shift4RouterData::try_from((amount, req))?; + let connector_req = Shift4PaymentsRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, - req: &types::PaymentsCompleteAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsCompleteAuthorizeType::get_url( self, req, connectors, )?) @@ -586,10 +614,10 @@ impl fn handle_response( &self, - data: &types::PaymentsCompleteAuthorizeRouterData, + data: &PaymentsCompleteAuthorizeRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: shift4::Shift4NonThreeDsResponse = res .response .parse_struct("Shift4NonThreeDsResponse") @@ -598,7 +626,7 @@ impl event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -608,19 +636,19 @@ impl fn get_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { self.build_error_response(res, event_builder) } } -impl ConnectorIntegration for Shift4 { +impl ConnectorIntegration for Shift4 { fn get_headers( &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -630,28 +658,34 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, + _req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!("{}refunds", self.base_url(connectors),)) } fn get_request_body( &self, - req: &types::RefundsRouterData, - _connectors: &settings::Connectors, + req: &RefundsRouterData, + _connectors: &Connectors, ) -> CustomResult { - let connector_req = shift4::Shift4RefundRequest::try_from(req)?; + let amount = convert_amount( + self.amount_converter, + req.request.minor_refund_amount, + req.request.currency, + )?; + let connector_router_data = shift4::Shift4RouterData::try_from((amount, req))?; + let connector_req = Shift4RefundRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) .url(&types::RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundExecuteType::get_headers( @@ -666,10 +700,10 @@ impl ConnectorIntegration, + data: &RefundsRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult, errors::ConnectorError> { + res: Response, + ) -> CustomResult, errors::ConnectorError> { let response: shift4::RefundResponse = res .response .parse_struct("RefundResponse") @@ -678,7 +712,7 @@ impl ConnectorIntegration, ) -> CustomResult { self.build_error_response(res, event_builder) } } -impl ConnectorIntegration for Shift4 { +impl ConnectorIntegration for Shift4 { fn get_headers( &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -710,8 +744,8 @@ impl ConnectorIntegration CustomResult { let refund_id = req.request.get_connector_refund_id()?; Ok(format!( @@ -723,12 +757,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) + RequestBuilder::new() + .method(Method::Get) .url(&types::RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundSyncType::get_headers(self, req, connectors)?) @@ -738,17 +772,17 @@ impl ConnectorIntegration, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: shift4::RefundResponse = res.response .parse_struct("shift4 RefundResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -758,7 +792,7 @@ impl ConnectorIntegration, ) -> CustomResult { self.build_error_response(res, event_builder) @@ -766,10 +800,10 @@ impl ConnectorIntegration, + request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult { let details: shift4::Shift4WebhookObjectId = request .body @@ -799,18 +833,18 @@ impl api::IncomingWebhook for Shift4 { fn get_webhook_event_type( &self, - request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { let details: shift4::Shift4WebhookObjectEventType = request .body .parse_struct("Shift4WebhookObjectEventType") .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?; - Ok(api::IncomingWebhookEvent::from(details.event_type)) + Ok(IncomingWebhookEvent::from(details.event_type)) } fn get_webhook_resource_object( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { let details: shift4::Shift4WebhookObjectResource = request .body @@ -821,3 +855,5 @@ impl api::IncomingWebhook for Shift4 { Ok(Box::new(details.data)) } } + +impl ConnectorSpecifications for Shift4 {} diff --git a/crates/router/src/connector/shift4/transformers.rs b/crates/hyperswitch_connectors/src/connectors/shift4/transformers.rs similarity index 56% rename from crates/router/src/connector/shift4/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/shift4/transformers.rs index 13c0395cf9f4..1973e622be1b 100644 --- a/crates/router/src/connector/shift4/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/shift4/transformers.rs @@ -1,19 +1,38 @@ -use api_models::payments; +use api_models::webhooks::IncomingWebhookEvent; use cards::CardNumber; -use common_utils::pii::SecretSerdeValue; +use common_enums::enums; +use common_utils::{ + pii::{self, SecretSerdeValue}, + request::Method, + types::MinorUnit, +}; use error_stack::ResultExt; +use hyperswitch_domain_models::{ + payment_method_data::{ + BankRedirectData, BankTransferData, Card as CardData, GiftCardData, PaymentMethodData, + VoucherData, WalletData, + }, + router_data::{ConnectorAuthType, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::{ + CompleteAuthorizeData, PaymentsAuthorizeData, PaymentsPreProcessingData, ResponseId, + }, + router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData}, + types::{PaymentsPreProcessingRouterData, RefundsRouterData}, +}; +use hyperswitch_interfaces::errors; use masking::Secret; use serde::{Deserialize, Serialize}; use url::Url; use crate::{ - connector::utils::{ + types::{ + PaymentsPreprocessingResponseRouterData, RefundsResponseRouterData, ResponseRouterData, + }, + utils::{ self, to_connector_meta, PaymentsAuthorizeRequestData, - PaymentsCompleteAuthorizeRequestData, PaymentsPreProcessingData, RouterData, + PaymentsCompleteAuthorizeRequestData, PaymentsPreProcessingRequestData, RouterData as _, }, - core::errors, - pii, services, - types::{self, api, domain, storage::enums, transformers::ForeignFrom}, }; type Error = error_stack::Report; @@ -23,12 +42,26 @@ trait Shift4AuthorizePreprocessingCommon { fn get_router_return_url(&self) -> Option; fn get_email_optional(&self) -> Option; fn get_complete_authorize_url(&self) -> Option; - fn get_amount_required(&self) -> Result; - fn get_currency_required(&self) -> Result; - fn get_payment_method_data_required(&self) -> Result; + fn get_currency_required(&self) -> Result; + fn get_payment_method_data_required(&self) -> Result; +} + +pub struct Shift4RouterData { + pub amount: MinorUnit, + pub router_data: T, +} + +impl TryFrom<(MinorUnit, T)> for Shift4RouterData { + type Error = error_stack::Report; + fn try_from((amount, item): (MinorUnit, T)) -> Result { + Ok(Self { + amount, + router_data: item, + }) + } } -impl Shift4AuthorizePreprocessingCommon for types::PaymentsAuthorizeData { +impl Shift4AuthorizePreprocessingCommon for PaymentsAuthorizeData { fn get_email_optional(&self) -> Option { self.email.clone() } @@ -37,18 +70,14 @@ impl Shift4AuthorizePreprocessingCommon for types::PaymentsAuthorizeData { self.complete_authorize_url.clone() } - fn get_amount_required(&self) -> Result> { - Ok(self.amount) - } - fn get_currency_required( &self, - ) -> Result> { + ) -> Result> { Ok(self.currency) } fn get_payment_method_data_required( &self, - ) -> Result> { + ) -> Result> { Ok(self.payment_method_data.clone()) } @@ -61,7 +90,7 @@ impl Shift4AuthorizePreprocessingCommon for types::PaymentsAuthorizeData { } } -impl Shift4AuthorizePreprocessingCommon for types::PaymentsPreProcessingData { +impl Shift4AuthorizePreprocessingCommon for PaymentsPreProcessingData { fn get_email_optional(&self) -> Option { self.email.clone() } @@ -70,14 +99,10 @@ impl Shift4AuthorizePreprocessingCommon for types::PaymentsPreProcessingData { self.complete_authorize_url.clone() } - fn get_amount_required(&self) -> Result { - self.get_amount() - } - - fn get_currency_required(&self) -> Result { + fn get_currency_required(&self) -> Result { self.get_currency() } - fn get_payment_method_data_required(&self) -> Result { + fn get_payment_method_data_required(&self) -> Result { self.payment_method_data.clone().ok_or( errors::ConnectorError::MissingRequiredField { field_name: "payment_method_data", @@ -95,7 +120,7 @@ impl Shift4AuthorizePreprocessingCommon for types::PaymentsPreProcessingData { } #[derive(Debug, Serialize)] pub struct Shift4PaymentsRequest { - amount: String, + amount: MinorUnit, currency: enums::Currency, captured: bool, #[serde(flatten)] @@ -195,19 +220,19 @@ pub enum CardPayment { CardToken(Secret), } -impl TryFrom<&types::RouterData> +impl TryFrom<&Shift4RouterData<&RouterData>> for Shift4PaymentsRequest where Req: Shift4AuthorizePreprocessingCommon, { type Error = Error; fn try_from( - item: &types::RouterData, + item: &Shift4RouterData<&RouterData>, ) -> Result { - let submit_for_settlement = item.request.is_automatic_capture()?; - let amount = item.request.get_amount_required()?.to_string(); - let currency = item.request.get_currency_required()?; - let payment_method = Shift4PaymentMethod::try_from(item)?; + let submit_for_settlement = item.router_data.request.is_automatic_capture()?; + let amount = item.amount.to_owned(); + let currency = item.router_data.request.get_currency_required()?; + let payment_method = Shift4PaymentMethod::try_from(item.router_data)?; Ok(Self { amount, currency, @@ -217,39 +242,36 @@ where } } -impl TryFrom<&types::RouterData> - for Shift4PaymentMethod +impl TryFrom<&RouterData> for Shift4PaymentMethod where Req: Shift4AuthorizePreprocessingCommon, { type Error = Error; - fn try_from( - item: &types::RouterData, - ) -> Result { + fn try_from(item: &RouterData) -> Result { match item.request.get_payment_method_data_required()? { - domain::PaymentMethodData::Card(ref ccard) => Self::try_from((item, ccard)), - domain::PaymentMethodData::BankRedirect(ref redirect) => { - Self::try_from((item, redirect)) - } - domain::PaymentMethodData::Wallet(ref wallet_data) => Self::try_from(wallet_data), - domain::PaymentMethodData::BankTransfer(ref bank_transfer_data) => { + PaymentMethodData::Card(ref ccard) => Self::try_from((item, ccard)), + PaymentMethodData::BankRedirect(ref redirect) => Self::try_from((item, redirect)), + PaymentMethodData::Wallet(ref wallet_data) => Self::try_from(wallet_data), + PaymentMethodData::BankTransfer(ref bank_transfer_data) => { Self::try_from(bank_transfer_data.as_ref()) } - domain::PaymentMethodData::Voucher(ref voucher_data) => Self::try_from(voucher_data), - domain::PaymentMethodData::GiftCard(ref giftcard_data) => { + PaymentMethodData::Voucher(ref voucher_data) => Self::try_from(voucher_data), + PaymentMethodData::GiftCard(ref giftcard_data) => { Self::try_from(giftcard_data.as_ref()) } - domain::PaymentMethodData::CardRedirect(_) - | domain::PaymentMethodData::PayLater(_) - | domain::PaymentMethodData::BankDebit(_) - | domain::PaymentMethodData::Crypto(_) - | domain::PaymentMethodData::MandatePayment - | domain::PaymentMethodData::Reward - | domain::PaymentMethodData::RealTimePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + PaymentMethodData::CardRedirect(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Shift4"), ) @@ -259,37 +281,38 @@ where } } -impl TryFrom<&domain::WalletData> for Shift4PaymentMethod { +impl TryFrom<&WalletData> for Shift4PaymentMethod { type Error = Error; - fn try_from(wallet_data: &domain::WalletData) -> Result { + fn try_from(wallet_data: &WalletData) -> Result { match wallet_data { - domain::WalletData::AliPayRedirect(_) - | domain::WalletData::ApplePay(_) - | domain::WalletData::WeChatPayRedirect(_) - | domain::WalletData::AliPayQr(_) - | domain::WalletData::AliPayHkRedirect(_) - | domain::WalletData::MomoRedirect(_) - | domain::WalletData::KakaoPayRedirect(_) - | domain::WalletData::GoPayRedirect(_) - | domain::WalletData::GcashRedirect(_) - | domain::WalletData::ApplePayRedirect(_) - | domain::WalletData::ApplePayThirdPartySdk(_) - | domain::WalletData::DanaRedirect {} - | domain::WalletData::GooglePay(_) - | domain::WalletData::GooglePayRedirect(_) - | domain::WalletData::GooglePayThirdPartySdk(_) - | domain::WalletData::MbWayRedirect(_) - | domain::WalletData::MobilePayRedirect(_) - | domain::WalletData::PaypalRedirect(_) - | domain::WalletData::PaypalSdk(_) - | domain::WalletData::SamsungPay(_) - | domain::WalletData::TwintRedirect {} - | domain::WalletData::VippsRedirect {} - | domain::WalletData::TouchNGoRedirect(_) - | domain::WalletData::WeChatPayQr(_) - | domain::WalletData::CashappQr(_) - | domain::WalletData::SwishQr(_) - | domain::WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( + WalletData::AliPayRedirect(_) + | WalletData::ApplePay(_) + | WalletData::WeChatPayRedirect(_) + | WalletData::AliPayQr(_) + | WalletData::AliPayHkRedirect(_) + | WalletData::MomoRedirect(_) + | WalletData::KakaoPayRedirect(_) + | WalletData::GoPayRedirect(_) + | WalletData::GcashRedirect(_) + | WalletData::ApplePayRedirect(_) + | WalletData::ApplePayThirdPartySdk(_) + | WalletData::DanaRedirect {} + | WalletData::GooglePay(_) + | WalletData::GooglePayRedirect(_) + | WalletData::GooglePayThirdPartySdk(_) + | WalletData::MbWayRedirect(_) + | WalletData::MobilePayRedirect(_) + | WalletData::PaypalRedirect(_) + | WalletData::PaypalSdk(_) + | WalletData::Paze(_) + | WalletData::SamsungPay(_) + | WalletData::TwintRedirect {} + | WalletData::VippsRedirect {} + | WalletData::TouchNGoRedirect(_) + | WalletData::WeChatPayQr(_) + | WalletData::CashappQr(_) + | WalletData::SwishQr(_) + | WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Shift4"), ) .into()), @@ -297,24 +320,24 @@ impl TryFrom<&domain::WalletData> for Shift4PaymentMethod { } } -impl TryFrom<&domain::BankTransferData> for Shift4PaymentMethod { +impl TryFrom<&BankTransferData> for Shift4PaymentMethod { type Error = Error; - fn try_from(bank_transfer_data: &domain::BankTransferData) -> Result { + fn try_from(bank_transfer_data: &BankTransferData) -> Result { match bank_transfer_data { - domain::BankTransferData::MultibancoBankTransfer { .. } - | domain::BankTransferData::AchBankTransfer { .. } - | domain::BankTransferData::SepaBankTransfer { .. } - | domain::BankTransferData::BacsBankTransfer { .. } - | domain::BankTransferData::PermataBankTransfer { .. } - | domain::BankTransferData::BcaBankTransfer { .. } - | domain::BankTransferData::BniVaBankTransfer { .. } - | domain::BankTransferData::BriVaBankTransfer { .. } - | domain::BankTransferData::CimbVaBankTransfer { .. } - | domain::BankTransferData::DanamonVaBankTransfer { .. } - | domain::BankTransferData::MandiriVaBankTransfer { .. } - | domain::BankTransferData::Pix { .. } - | domain::BankTransferData::Pse {} - | domain::BankTransferData::LocalBankTransfer { .. } => { + BankTransferData::MultibancoBankTransfer { .. } + | BankTransferData::AchBankTransfer { .. } + | BankTransferData::SepaBankTransfer { .. } + | BankTransferData::BacsBankTransfer { .. } + | BankTransferData::PermataBankTransfer { .. } + | BankTransferData::BcaBankTransfer { .. } + | BankTransferData::BniVaBankTransfer { .. } + | BankTransferData::BriVaBankTransfer { .. } + | BankTransferData::CimbVaBankTransfer { .. } + | BankTransferData::DanamonVaBankTransfer { .. } + | BankTransferData::MandiriVaBankTransfer { .. } + | BankTransferData::Pix { .. } + | BankTransferData::Pse {} + | BankTransferData::LocalBankTransfer { .. } => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Shift4"), ) @@ -324,24 +347,24 @@ impl TryFrom<&domain::BankTransferData> for Shift4PaymentMethod { } } -impl TryFrom<&domain::VoucherData> for Shift4PaymentMethod { +impl TryFrom<&VoucherData> for Shift4PaymentMethod { type Error = Error; - fn try_from(voucher_data: &domain::VoucherData) -> Result { + fn try_from(voucher_data: &VoucherData) -> Result { match voucher_data { - domain::VoucherData::Boleto(_) - | domain::VoucherData::Efecty - | domain::VoucherData::PagoEfectivo - | domain::VoucherData::RedCompra - | domain::VoucherData::RedPagos - | domain::VoucherData::Alfamart(_) - | domain::VoucherData::Indomaret(_) - | domain::VoucherData::Oxxo - | domain::VoucherData::SevenEleven(_) - | domain::VoucherData::Lawson(_) - | domain::VoucherData::MiniStop(_) - | domain::VoucherData::FamilyMart(_) - | domain::VoucherData::Seicomart(_) - | domain::VoucherData::PayEasy(_) => Err(errors::ConnectorError::NotImplemented( + VoucherData::Boleto(_) + | VoucherData::Efecty + | VoucherData::PagoEfectivo + | VoucherData::RedCompra + | VoucherData::RedPagos + | VoucherData::Alfamart(_) + | VoucherData::Indomaret(_) + | VoucherData::Oxxo + | VoucherData::SevenEleven(_) + | VoucherData::Lawson(_) + | VoucherData::MiniStop(_) + | VoucherData::FamilyMart(_) + | VoucherData::Seicomart(_) + | VoucherData::PayEasy(_) => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Shift4"), ) .into()), @@ -349,11 +372,11 @@ impl TryFrom<&domain::VoucherData> for Shift4PaymentMethod { } } -impl TryFrom<&domain::GiftCardData> for Shift4PaymentMethod { +impl TryFrom<&GiftCardData> for Shift4PaymentMethod { type Error = Error; - fn try_from(gift_card_data: &domain::GiftCardData) -> Result { + fn try_from(gift_card_data: &GiftCardData) -> Result { match gift_card_data { - domain::GiftCardData::Givex(_) | domain::GiftCardData::PaySafeCard {} => { + GiftCardData::Givex(_) | GiftCardData::PaySafeCard {} => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Shift4"), ) @@ -363,20 +386,13 @@ impl TryFrom<&domain::GiftCardData> for Shift4PaymentMethod { } } -impl - TryFrom<( - &types::RouterData, - &domain::Card, - )> for Shift4PaymentMethod +impl TryFrom<(&RouterData, &CardData)> for Shift4PaymentMethod where Req: Shift4AuthorizePreprocessingCommon, { type Error = Error; fn try_from( - (item, card): ( - &types::RouterData, - &domain::Card, - ), + (item, card): (&RouterData, &CardData), ) -> Result { let card_object = Card { number: card.card_number.clone(), @@ -406,20 +422,14 @@ where } } -impl - TryFrom<( - &types::RouterData, - &domain::BankRedirectData, - )> for Shift4PaymentMethod +impl TryFrom<(&RouterData, &BankRedirectData)> + for Shift4PaymentMethod where Req: Shift4AuthorizePreprocessingCommon, { type Error = Error; fn try_from( - (item, redirect_data): ( - &types::RouterData, - &domain::BankRedirectData, - ), + (item, redirect_data): (&RouterData, &BankRedirectData), ) -> Result { let flow = Flow::try_from(item.request.get_router_return_url())?; let method_type = PaymentMethodType::try_from(redirect_data)?; @@ -435,45 +445,47 @@ where } } -impl TryFrom<&types::RouterData> +impl TryFrom<&Shift4RouterData<&RouterData>> for Shift4PaymentsRequest { type Error = Error; fn try_from( - item: &types::RouterData, + item: &Shift4RouterData<&RouterData>, ) -> Result { - match &item.request.payment_method_data { - Some(domain::PaymentMethodData::Card(_)) => { + match &item.router_data.request.payment_method_data { + Some(PaymentMethodData::Card(_)) => { let card_token: Shift4CardToken = - to_connector_meta(item.request.connector_meta.clone())?; + to_connector_meta(item.router_data.request.connector_meta.clone())?; Ok(Self { - amount: item.request.amount.to_string(), - currency: item.request.currency, + amount: item.amount.to_owned(), + currency: item.router_data.request.currency, payment_method: Shift4PaymentMethod::CardsNon3DSRequest(Box::new( CardsNon3DSRequest { card: CardPayment::CardToken(card_token.id), - description: item.description.clone(), + description: item.router_data.description.clone(), }, )), - captured: item.request.is_auto_capture()?, + captured: item.router_data.request.is_auto_capture()?, }) } - Some(domain::PaymentMethodData::Wallet(_)) - | Some(domain::PaymentMethodData::GiftCard(_)) - | Some(domain::PaymentMethodData::CardRedirect(_)) - | Some(domain::PaymentMethodData::PayLater(_)) - | Some(domain::PaymentMethodData::BankDebit(_)) - | Some(domain::PaymentMethodData::BankRedirect(_)) - | Some(domain::PaymentMethodData::BankTransfer(_)) - | Some(domain::PaymentMethodData::Crypto(_)) - | Some(domain::PaymentMethodData::MandatePayment) - | Some(domain::PaymentMethodData::Voucher(_)) - | Some(domain::PaymentMethodData::Reward) - | Some(domain::PaymentMethodData::RealTimePayment(_)) - | Some(domain::PaymentMethodData::Upi(_)) - | Some(domain::PaymentMethodData::OpenBanking(_)) - | Some(domain::PaymentMethodData::CardToken(_)) - | Some(domain::PaymentMethodData::NetworkToken(_)) + Some(PaymentMethodData::Wallet(_)) + | Some(PaymentMethodData::GiftCard(_)) + | Some(PaymentMethodData::CardRedirect(_)) + | Some(PaymentMethodData::PayLater(_)) + | Some(PaymentMethodData::BankDebit(_)) + | Some(PaymentMethodData::BankRedirect(_)) + | Some(PaymentMethodData::BankTransfer(_)) + | Some(PaymentMethodData::Crypto(_)) + | Some(PaymentMethodData::MandatePayment) + | Some(PaymentMethodData::Voucher(_)) + | Some(PaymentMethodData::Reward) + | Some(PaymentMethodData::RealTimePayment(_)) + | Some(PaymentMethodData::MobilePayment(_)) + | Some(PaymentMethodData::Upi(_)) + | Some(PaymentMethodData::OpenBanking(_)) + | Some(PaymentMethodData::CardToken(_)) + | Some(PaymentMethodData::NetworkToken(_)) + | Some(PaymentMethodData::CardDetailsForNetworkTransactionId(_)) | None => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Shift4"), ) @@ -482,28 +494,28 @@ impl TryFrom<&types::RouterData for PaymentMethodType { +impl TryFrom<&BankRedirectData> for PaymentMethodType { type Error = Error; - fn try_from(value: &domain::BankRedirectData) -> Result { + fn try_from(value: &BankRedirectData) -> Result { match value { - domain::BankRedirectData::Eps { .. } => Ok(Self::Eps), - domain::BankRedirectData::Giropay { .. } => Ok(Self::Giropay), - domain::BankRedirectData::Ideal { .. } => Ok(Self::Ideal), - domain::BankRedirectData::Sofort { .. } => Ok(Self::Sofort), - domain::BankRedirectData::BancontactCard { .. } - | domain::BankRedirectData::Blik { .. } - | domain::BankRedirectData::Trustly { .. } - | domain::BankRedirectData::Przelewy24 { .. } - | domain::BankRedirectData::Bizum {} - | domain::BankRedirectData::Interac { .. } - | domain::BankRedirectData::OnlineBankingCzechRepublic { .. } - | domain::BankRedirectData::OnlineBankingFinland { .. } - | domain::BankRedirectData::OnlineBankingPoland { .. } - | domain::BankRedirectData::OnlineBankingSlovakia { .. } - | domain::BankRedirectData::OpenBankingUk { .. } - | domain::BankRedirectData::OnlineBankingFpx { .. } - | domain::BankRedirectData::OnlineBankingThailand { .. } - | domain::BankRedirectData::LocalBankRedirect {} => { + BankRedirectData::Eps { .. } => Ok(Self::Eps), + BankRedirectData::Giropay { .. } => Ok(Self::Giropay), + BankRedirectData::Ideal { .. } => Ok(Self::Ideal), + BankRedirectData::Sofort { .. } => Ok(Self::Sofort), + BankRedirectData::BancontactCard { .. } + | BankRedirectData::Blik { .. } + | BankRedirectData::Trustly { .. } + | BankRedirectData::Przelewy24 { .. } + | BankRedirectData::Bizum {} + | BankRedirectData::Interac { .. } + | BankRedirectData::OnlineBankingCzechRepublic { .. } + | BankRedirectData::OnlineBankingFinland { .. } + | BankRedirectData::OnlineBankingPoland { .. } + | BankRedirectData::OnlineBankingSlovakia { .. } + | BankRedirectData::OpenBankingUk { .. } + | BankRedirectData::OnlineBankingFpx { .. } + | BankRedirectData::OnlineBankingThailand { .. } + | BankRedirectData::LocalBankRedirect {} => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Shift4"), ) @@ -522,14 +534,12 @@ impl TryFrom> for Flow { } } -impl TryFrom<&types::RouterData> for Billing +impl TryFrom<&RouterData> for Billing where Req: Shift4AuthorizePreprocessingCommon, { type Error = Error; - fn try_from( - item: &types::RouterData, - ) -> Result { + fn try_from(item: &RouterData) -> Result { let billing_address = item .get_optional_billing() .as_ref() @@ -545,7 +555,9 @@ where } } -fn get_address_details(address_details: Option<&payments::AddressDetails>) -> Option

{ +fn get_address_details( + address_details: Option<&hyperswitch_domain_models::address::AddressDetails>, +) -> Option
{ address_details.map(|address| Address { line1: address.line1.clone(), line2: address.line1.clone(), @@ -561,10 +573,10 @@ pub struct Shift4AuthType { pub(super) api_key: Secret, } -impl TryFrom<&types::ConnectorAuthType> for Shift4AuthType { +impl TryFrom<&ConnectorAuthType> for Shift4AuthType { type Error = Error; - fn try_from(item: &types::ConnectorAuthType) -> Result { - if let types::ConnectorAuthType::HeaderKey { api_key } = item { + fn try_from(item: &ConnectorAuthType) -> Result { + if let ConnectorAuthType::HeaderKey { api_key } = item { Ok(Self { api_key: api_key.to_owned(), }) @@ -583,23 +595,24 @@ pub enum Shift4PaymentStatus { Pending, } -impl ForeignFrom<(bool, Option<&NextAction>, Shift4PaymentStatus)> for enums::AttemptStatus { - fn foreign_from(item: (bool, Option<&NextAction>, Shift4PaymentStatus)) -> Self { - let (captured, next_action, payment_status) = item; - match payment_status { - Shift4PaymentStatus::Successful => { - if captured { - Self::Charged - } else { - Self::Authorized - } +fn get_status( + captured: bool, + next_action: Option<&NextAction>, + payment_status: Shift4PaymentStatus, +) -> enums::AttemptStatus { + match payment_status { + Shift4PaymentStatus::Successful => { + if captured { + enums::AttemptStatus::Charged + } else { + enums::AttemptStatus::Authorized } - Shift4PaymentStatus::Failed => Self::Failure, - Shift4PaymentStatus::Pending => match next_action { - Some(NextAction::Redirect) => Self::AuthenticationPending, - Some(NextAction::Wait) | Some(NextAction::None) | None => Self::Pending, - }, } + Shift4PaymentStatus::Failed => enums::AttemptStatus::Failure, + Shift4PaymentStatus::Pending => match next_action { + Some(NextAction::Redirect) => enums::AttemptStatus::AuthenticationPending, + Some(NextAction::Wait) | Some(NextAction::None) | None => enums::AttemptStatus::Pending, + }, } } @@ -684,7 +697,7 @@ pub struct Token { #[derive(Default, Debug, Deserialize, Serialize)] pub struct ThreeDSecureInfo { - pub amount: i64, + pub amount: MinorUnit, pub currency: String, pub enrolled: bool, #[serde(rename = "liabilityShift")] @@ -721,31 +734,31 @@ pub struct Shift4CardToken { pub id: Secret, } -impl TryFrom> - for types::PaymentsPreProcessingRouterData +impl TryFrom> + for PaymentsPreProcessingRouterData { type Error = Error; fn try_from( - item: types::PaymentsPreprocessingResponseRouterData, + item: PaymentsPreprocessingResponseRouterData, ) -> Result { let redirection_data = item .response .redirect_url - .map(|url| services::RedirectForm::from((url, services::Method::Get))); + .map(|url| RedirectForm::from((url, Method::Get))); Ok(Self { status: if redirection_data.is_some() { enums::AttemptStatus::AuthenticationPending } else { enums::AttemptStatus::Pending }, - request: types::PaymentsPreProcessingData { + request: PaymentsPreProcessingData { enrolled_for_3ds: item.response.enrolled, ..item.data.request }, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::NoResponseId, - redirection_data, - mandate_reference: None, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::NoResponseId, + redirection_data: Box::new(redirection_data), + mandate_reference: Box::new(None), connector_metadata: Some( serde_json::to_value(Shift4CardToken { id: item.response.token.id, @@ -762,38 +775,33 @@ impl TryFrom - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = Error; fn try_from( - item: types::ResponseRouterData< - F, - Shift4NonThreeDsResponse, - T, - types::PaymentsResponseData, - >, + item: ResponseRouterData, ) -> Result { - let connector_id = types::ResponseId::ConnectorTransactionId(item.response.id.clone()); + let connector_id = ResponseId::ConnectorTransactionId(item.response.id.clone()); Ok(Self { - status: enums::AttemptStatus::foreign_from(( + status: get_status( item.response.captured, item.response .flow .as_ref() .and_then(|flow| flow.next_action.as_ref()), item.response.status, - )), - response: Ok(types::PaymentsResponseData::TransactionResponse { + ), + response: Ok(PaymentsResponseData::TransactionResponse { resource_id: connector_id, - redirection_data: item - .response - .flow - .and_then(|flow| flow.redirect) - .and_then(|redirect| redirect.redirect_url) - .map(|url| services::RedirectForm::from((url, services::Method::Get))), - mandate_reference: None, + redirection_data: Box::new( + item.response + .flow + .and_then(|flow| flow.redirect) + .and_then(|redirect| redirect.redirect_url) + .map(|url| RedirectForm::from((url, Method::Get))), + ), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(item.response.id), @@ -811,15 +819,15 @@ impl #[serde(rename_all = "camelCase")] pub struct Shift4RefundRequest { charge_id: String, - amount: i64, + amount: MinorUnit, } -impl TryFrom<&types::RefundsRouterData> for Shift4RefundRequest { +impl TryFrom<&Shift4RouterData<&RefundsRouterData>> for Shift4RefundRequest { type Error = Error; - fn try_from(item: &types::RefundsRouterData) -> Result { + fn try_from(item: &Shift4RouterData<&RefundsRouterData>) -> Result { Ok(Self { - charge_id: item.request.connector_transaction_id.clone(), - amount: item.request.refund_amount, + charge_id: item.router_data.request.connector_transaction_id.clone(), + amount: item.amount.to_owned(), }) } } @@ -852,16 +860,14 @@ pub enum Shift4RefundStatus { Failed, } -impl TryFrom> - for types::RefundsRouterData -{ +impl TryFrom> for RefundsRouterData { type Error = Error; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { let refund_status = enums::RefundStatus::from(item.response.status); Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item.response.id, refund_status, }), @@ -870,16 +876,14 @@ impl TryFrom> } } -impl TryFrom> - for types::RefundsRouterData -{ +impl TryFrom> for RefundsRouterData { type Error = Error; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { let refund_status = enums::RefundStatus::from(item.response.status); Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item.response.id, refund_status, }), @@ -913,7 +917,7 @@ pub fn is_refund_event(event: &Shift4WebhookEvent) -> bool { matches!(event, Shift4WebhookEvent::ChargeRefunded) } -impl From for api::IncomingWebhookEvent { +impl From for IncomingWebhookEvent { fn from(event: Shift4WebhookEvent) -> Self { match event { Shift4WebhookEvent::ChargeSucceeded | Shift4WebhookEvent::ChargeUpdated => { diff --git a/crates/hyperswitch_connectors/src/connectors/square.rs b/crates/hyperswitch_connectors/src/connectors/square.rs index bf2cd4e1aceb..e07d85800ffd 100644 --- a/crates/hyperswitch_connectors/src/connectors/square.rs +++ b/crates/hyperswitch_connectors/src/connectors/square.rs @@ -34,7 +34,10 @@ use hyperswitch_domain_models::{ }, }; use hyperswitch_interfaces::{ - api::{self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorValidation}, + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, configs::Connectors, consts, errors, events::connector_api_logs::ConnectorEvent, @@ -156,14 +159,17 @@ impl ConnectorCommon for Square { } impl ConnectorValidation for Square { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( utils::construct_not_implemented_error_report(capture_method, self.id()), ), @@ -920,3 +926,5 @@ impl IncomingWebhook for Square { }) } } + +impl ConnectorSpecifications for Square {} diff --git a/crates/hyperswitch_connectors/src/connectors/square/transformers.rs b/crates/hyperswitch_connectors/src/connectors/square/transformers.rs index 4e2b30a574b3..5abc93b3f882 100644 --- a/crates/hyperswitch_connectors/src/connectors/square/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/square/transformers.rs @@ -85,6 +85,7 @@ impl TryFrom<(&types::TokenizationRouterData, PayLaterData)> for SquareTokenRequ PayLaterData::AfterpayClearpayRedirect { .. } | PayLaterData::KlarnaRedirect { .. } | PayLaterData::KlarnaSdk { .. } + | PayLaterData::KlarnaCheckout {} | PayLaterData::AffirmRedirect { .. } | PayLaterData::PayBrightRedirect { .. } | PayLaterData::WalleyRedirect { .. } @@ -119,6 +120,7 @@ impl TryFrom<(&types::TokenizationRouterData, WalletData)> for SquareTokenReques | WalletData::MobilePayRedirect(_) | WalletData::PaypalRedirect(_) | WalletData::PaypalSdk(_) + | WalletData::Paze(_) | WalletData::SamsungPay(_) | WalletData::TwintRedirect {} | WalletData::VippsRedirect {} @@ -172,13 +174,17 @@ impl TryFrom<&types::TokenizationRouterData> for SquareTokenRequest { | PaymentMethodData::MandatePayment | PaymentMethodData::Reward | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) | PaymentMethodData::Upi(_) | PaymentMethodData::Voucher(_) | PaymentMethodData::OpenBanking(_) | PaymentMethodData::CardToken(_) - | PaymentMethodData::NetworkToken(_) => Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("Square"), - ))?, + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Square"), + ))? + } } } } @@ -263,6 +269,9 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for SquarePaymentsRequest { PaymentMethodToken::ApplePayDecrypt(_) => Err( unimplemented_payment_method!("Apple Pay", "Simplified", "Square"), )?, + PaymentMethodToken::PazeDecrypt(_) => { + Err(unimplemented_payment_method!("Paze", "Square"))? + } }, amount_money: SquarePaymentsAmountData { amount: item.request.amount, @@ -286,13 +295,17 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for SquarePaymentsRequest { | PaymentMethodData::MandatePayment | PaymentMethodData::Reward | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) | PaymentMethodData::Upi(_) | PaymentMethodData::Voucher(_) | PaymentMethodData::OpenBanking(_) | PaymentMethodData::CardToken(_) - | PaymentMethodData::NetworkToken(_) => Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("Square"), - ))?, + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Square"), + ))? + } } } } @@ -375,8 +388,8 @@ impl TryFrom, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( utils::construct_not_supported_error_report(capture_method, self.id()), ), @@ -916,3 +922,5 @@ impl webhooks::IncomingWebhook for Stax { Ok(Box::new(reference_object)) } } + +impl ConnectorSpecifications for Stax {} diff --git a/crates/hyperswitch_connectors/src/connectors/stax/transformers.rs b/crates/hyperswitch_connectors/src/connectors/stax/transformers.rs index f7b2016a3705..a675e3040e39 100644 --- a/crates/hyperswitch_connectors/src/connectors/stax/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/stax/transformers.rs @@ -82,6 +82,9 @@ impl TryFrom<&StaxRouterData<&types::PaymentsAuthorizeRouterData>> for StaxPayme PaymentMethodToken::ApplePayDecrypt(_) => Err( unimplemented_payment_method!("Apple Pay", "Simplified", "Stax"), )?, + PaymentMethodToken::PazeDecrypt(_) => { + Err(unimplemented_payment_method!("Paze", "Stax"))? + } }, idempotency_id: Some(item.router_data.connector_request_reference_id.clone()), }) @@ -99,6 +102,9 @@ impl TryFrom<&StaxRouterData<&types::PaymentsAuthorizeRouterData>> for StaxPayme PaymentMethodToken::ApplePayDecrypt(_) => Err( unimplemented_payment_method!("Apple Pay", "Simplified", "Stax"), )?, + PaymentMethodToken::PazeDecrypt(_) => { + Err(unimplemented_payment_method!("Paze", "Stax"))? + } }, idempotency_id: Some(item.router_data.connector_request_reference_id.clone()), }) @@ -112,15 +118,19 @@ impl TryFrom<&StaxRouterData<&types::PaymentsAuthorizeRouterData>> for StaxPayme | PaymentMethodData::MandatePayment | PaymentMethodData::Reward | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) | PaymentMethodData::Voucher(_) | PaymentMethodData::GiftCard(_) | PaymentMethodData::CardRedirect(_) | PaymentMethodData::Upi(_) | PaymentMethodData::OpenBanking(_) | PaymentMethodData::CardToken(_) - | PaymentMethodData::NetworkToken(_) => Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("Stax"), - ))?, + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Stax"), + ))? + } } } } @@ -263,15 +273,19 @@ impl TryFrom<&types::TokenizationRouterData> for StaxTokenRequest { | PaymentMethodData::MandatePayment | PaymentMethodData::Reward | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) | PaymentMethodData::Voucher(_) | PaymentMethodData::GiftCard(_) | PaymentMethodData::CardRedirect(_) | PaymentMethodData::Upi(_) | PaymentMethodData::OpenBanking(_) | PaymentMethodData::CardToken(_) - | PaymentMethodData::NetworkToken(_) => Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("Stax"), - ))?, + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Stax"), + ))? + } } } } @@ -360,8 +374,8 @@ impl TryFrom { pub amount: FloatMajorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub order_amount: FloatMajorUnit, pub shipping: FloatMajorUnit, pub router_data: T, } -impl From<(FloatMajorUnit, FloatMajorUnit, T)> for TaxjarRouterData { - fn from((amount, shipping, item): (FloatMajorUnit, FloatMajorUnit, T)) -> Self { +impl From<(FloatMajorUnit, FloatMajorUnit, FloatMajorUnit, T)> for TaxjarRouterData { + fn from( + (amount, order_amount, shipping, item): (FloatMajorUnit, FloatMajorUnit, FloatMajorUnit, T), + ) -> Self { Self { amount, + order_amount, shipping, router_data: item, } @@ -49,7 +53,7 @@ pub struct LineItem { id: Option, quantity: Option, product_tax_code: Option, - unit_price: Option, + unit_price: Option, } #[derive(Default, Debug, Serialize, Eq, PartialEq)] @@ -69,8 +73,6 @@ impl TryFrom<&TaxjarRouterData<&types::PaymentsTaxCalculationRouterData>> item: &TaxjarRouterData<&types::PaymentsTaxCalculationRouterData>, ) -> Result { let request = &item.router_data.request; - let currency = item.router_data.request.currency; - let currency_unit = &api::CurrencyUnit::Base; let shipping = &item .router_data .request @@ -87,16 +89,11 @@ impl TryFrom<&TaxjarRouterData<&types::PaymentsTaxCalculationRouterData>> order_details .iter() .map(|line_item| { - let unit_price = utils::get_amount_as_f64( - currency_unit, - line_item.amount, - currency, - )?; Ok(LineItem { id: line_item.product_id.clone(), quantity: Some(line_item.quantity), product_tax_code: line_item.product_tax_code.clone(), - unit_price: Some(unit_price), + unit_price: Some(item.order_amount), }) }) .collect(); diff --git a/crates/hyperswitch_connectors/src/connectors/thunes.rs b/crates/hyperswitch_connectors/src/connectors/thunes.rs index 972ba0d7dd30..b26c0e67fec3 100644 --- a/crates/hyperswitch_connectors/src/connectors/thunes.rs +++ b/crates/hyperswitch_connectors/src/connectors/thunes.rs @@ -26,7 +26,10 @@ use hyperswitch_domain_models::{ }, }; use hyperswitch_interfaces::{ - api::{self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorValidation}, + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, configs::Connectors, errors, events::connector_api_logs::ConnectorEvent, @@ -561,3 +564,5 @@ impl webhooks::IncomingWebhook for Thunes { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for Thunes {} diff --git a/crates/hyperswitch_connectors/src/connectors/thunes/transformers.rs b/crates/hyperswitch_connectors/src/connectors/thunes/transformers.rs index c0ee0fdae944..ca18179ff171 100644 --- a/crates/hyperswitch_connectors/src/connectors/thunes/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/thunes/transformers.rs @@ -129,8 +129,8 @@ impl TryFrom + Sync), +} -#[derive(Debug, Clone)] -pub struct Tsys; - +impl Tsys { + pub fn new() -> &'static Self { + &Self { + amount_converter: &StringMinorUnitForConnector, + } + } +} impl api::Payment for Tsys {} impl api::PaymentSession for Tsys {} impl api::ConnectorAccessToken for Tsys {} @@ -95,14 +105,17 @@ impl ConnectorCommon for Tsys { } impl ConnectorValidation for Tsys { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( utils::construct_not_supported_error_report(capture_method, self.id()), ), @@ -156,7 +169,14 @@ impl ConnectorIntegration CustomResult { - let connector_req = tsys::TsysPaymentsRequest::try_from(req)?; + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + let connector_router_data = tsys::TsysRouterData::from((amount, req)); + let connector_req: transformers::TsysPaymentsRequest = + tsys::TsysPaymentsRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -316,7 +336,13 @@ impl ConnectorIntegration fo req: &PaymentsCaptureRouterData, _connectors: &Connectors, ) -> CustomResult { - let connector_req = tsys::TsysPaymentsCaptureRequest::try_from(req)?; + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount_to_capture, + req.request.currency, + )?; + let connector_router_data = tsys::TsysRouterData::from((amount, req)); + let connector_req = tsys::TsysPaymentsCaptureRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -473,7 +499,14 @@ impl ConnectorIntegration for Tsys { req: &RefundsRouterData, _connectors: &Connectors, ) -> CustomResult { - let connector_req = tsys::TsysRefundRequest::try_from(req)?; + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_refund_amount, + req.request.currency, + )?; + let connector_router_data = tsys::TsysRouterData::from((amount, req)); + + let connector_req = tsys::TsysRefundRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -624,3 +657,5 @@ impl webhooks::IncomingWebhook for Tsys { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for Tsys {} diff --git a/crates/hyperswitch_connectors/src/connectors/tsys/transformers.rs b/crates/hyperswitch_connectors/src/connectors/tsys/transformers.rs index 8d96058c2336..27b9daaa5798 100644 --- a/crates/hyperswitch_connectors/src/connectors/tsys/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/tsys/transformers.rs @@ -1,4 +1,5 @@ use common_enums::enums; +use common_utils::types::StringMinorUnit; use error_stack::ResultExt; use hyperswitch_domain_models::{ payment_method_data::PaymentMethodData, @@ -7,13 +8,26 @@ use hyperswitch_domain_models::{ router_request_types::ResponseId, router_response_types::{PaymentsResponseData, RefundsResponseData}, types::{ - self, PaymentsCancelRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, - RefundSyncRouterData, RefundsRouterData, + self, PaymentsCancelRouterData, PaymentsSyncRouterData, RefundSyncRouterData, + RefundsRouterData, }, }; use hyperswitch_interfaces::errors; use masking::Secret; use serde::{Deserialize, Serialize}; +pub struct TsysRouterData { + pub amount: StringMinorUnit, + pub router_data: T, +} + +impl From<(StringMinorUnit, T)> for TsysRouterData { + fn from((amount, router_data): (StringMinorUnit, T)) -> Self { + Self { + amount, + router_data, + } + } +} use crate::{ types::{RefundsResponseRouterData, ResponseRouterData}, @@ -33,7 +47,7 @@ pub struct TsysPaymentAuthSaleRequest { device_id: Secret, transaction_key: Secret, card_data_source: String, - transaction_amount: String, + transaction_amount: StringMinorUnit, currency_code: enums::Currency, card_number: cards::CardNumber, expiration_date: Secret, @@ -46,9 +60,12 @@ pub struct TsysPaymentAuthSaleRequest { order_number: String, } -impl TryFrom<&types::PaymentsAuthorizeRouterData> for TsysPaymentsRequest { +impl TryFrom<&TsysRouterData<&types::PaymentsAuthorizeRouterData>> for TsysPaymentsRequest { type Error = error_stack::Report; - fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result { + fn try_from( + item_data: &TsysRouterData<&types::PaymentsAuthorizeRouterData>, + ) -> Result { + let item = item_data.router_data.clone(); match item.request.payment_method_data.clone() { PaymentMethodData::Card(ccard) => { let connector_auth: TsysAuthType = @@ -57,7 +74,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for TsysPaymentsRequest { device_id: connector_auth.device_id, transaction_key: connector_auth.transaction_key, card_data_source: "INTERNET".to_string(), - transaction_amount: item.request.amount.to_string(), + transaction_amount: item_data.amount.clone(), currency_code: item.request.currency, card_number: ccard.card_number.clone(), expiration_date: ccard @@ -85,14 +102,18 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for TsysPaymentsRequest { | PaymentMethodData::MandatePayment | PaymentMethodData::Reward | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) | PaymentMethodData::Upi(_) | PaymentMethodData::Voucher(_) | PaymentMethodData::GiftCard(_) | PaymentMethodData::OpenBanking(_) | PaymentMethodData::CardToken(_) - | PaymentMethodData::NetworkToken(_) => Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("tsys"), - ))?, + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("tsys"), + ))? + } } } } @@ -223,8 +244,8 @@ fn get_error_response( fn get_payments_response(connector_response: TsysResponse) -> PaymentsResponseData { PaymentsResponseData::TransactionResponse { resource_id: ResponseId::ConnectorTransactionId(connector_response.transaction_id.clone()), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(connector_response.transaction_id), @@ -243,8 +264,8 @@ fn get_payments_sync_response( .transaction_id .clone(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some( @@ -427,7 +448,7 @@ pub struct TsysCaptureRequest { #[serde(rename = "deviceID")] device_id: Secret, transaction_key: Secret, - transaction_amount: String, + transaction_amount: StringMinorUnit, #[serde(rename = "transactionID")] transaction_id: String, #[serde(rename = "developerID")] @@ -436,21 +457,23 @@ pub struct TsysCaptureRequest { #[derive(Debug, Serialize)] #[serde(rename_all = "PascalCase")] - pub struct TsysPaymentsCaptureRequest { capture: TsysCaptureRequest, } -impl TryFrom<&PaymentsCaptureRouterData> for TsysPaymentsCaptureRequest { +impl TryFrom<&TsysRouterData<&types::PaymentsCaptureRouterData>> for TsysPaymentsCaptureRequest { type Error = error_stack::Report; - fn try_from(item: &PaymentsCaptureRouterData) -> Result { + fn try_from( + item_data: &TsysRouterData<&types::PaymentsCaptureRouterData>, + ) -> Result { + let item = item_data.router_data.clone(); let connector_auth: TsysAuthType = TsysAuthType::try_from(&item.connector_auth_type)?; let capture = TsysCaptureRequest { device_id: connector_auth.device_id, transaction_key: connector_auth.transaction_key, transaction_id: item.request.connector_transaction_id.clone(), developer_id: connector_auth.developer_id, - transaction_amount: item.request.amount_to_capture.to_string(), + transaction_amount: item_data.amount.clone(), }; Ok(Self { capture }) } @@ -463,7 +486,7 @@ pub struct TsysReturnRequest { #[serde(rename = "deviceID")] device_id: Secret, transaction_key: Secret, - transaction_amount: String, + transaction_amount: StringMinorUnit, #[serde(rename = "transactionID")] transaction_id: String, } @@ -475,14 +498,15 @@ pub struct TsysRefundRequest { return_request: TsysReturnRequest, } -impl TryFrom<&RefundsRouterData> for TsysRefundRequest { +impl TryFrom<&TsysRouterData<&RefundsRouterData>> for TsysRefundRequest { type Error = error_stack::Report; - fn try_from(item: &RefundsRouterData) -> Result { + fn try_from(item_data: &TsysRouterData<&RefundsRouterData>) -> Result { + let item = item_data.router_data; let connector_auth: TsysAuthType = TsysAuthType::try_from(&item.connector_auth_type)?; let return_request = TsysReturnRequest { device_id: connector_auth.device_id, transaction_key: connector_auth.transaction_key, - transaction_amount: item.request.refund_amount.to_string(), + transaction_amount: item_data.amount.clone(), transaction_id: item.request.connector_transaction_id.clone(), }; Ok(Self { return_request }) diff --git a/crates/hyperswitch_connectors/src/connectors/unified_authentication_service.rs b/crates/hyperswitch_connectors/src/connectors/unified_authentication_service.rs new file mode 100644 index 000000000000..ece7130bb1dd --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/unified_authentication_service.rs @@ -0,0 +1,418 @@ +pub mod transformers; + +use common_utils::{ + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, FloatMajorUnit, FloatMajorUnitForConnector}, +}; +use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::{ + router_data::{AccessToken, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + PostAuthenticate, PreAuthenticate, + }, + router_request_types::{ + unified_authentication_service::{ + UasAuthenticationResponseData, UasPostAuthenticationRequestData, + UasPreAuthenticationRequestData, + }, + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{UasPostAuthenticationRouterData, UasPreAuthenticationRouterData}, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + consts::NO_ERROR_MESSAGE, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks, +}; +use transformers as unified_authentication_service; + +use crate::{constants::headers, types::ResponseRouterData, utils}; + +#[derive(Clone)] +pub struct UnifiedAuthenticationService { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl UnifiedAuthenticationService { + pub fn new() -> &'static Self { + &Self { + amount_converter: &FloatMajorUnitForConnector, + } + } +} + +impl api::Payment for UnifiedAuthenticationService {} +impl api::PaymentSession for UnifiedAuthenticationService {} +impl api::ConnectorAccessToken for UnifiedAuthenticationService {} +impl api::MandateSetup for UnifiedAuthenticationService {} +impl api::PaymentAuthorize for UnifiedAuthenticationService {} +impl api::PaymentSync for UnifiedAuthenticationService {} +impl api::PaymentCapture for UnifiedAuthenticationService {} +impl api::PaymentVoid for UnifiedAuthenticationService {} +impl api::Refund for UnifiedAuthenticationService {} +impl api::RefundExecute for UnifiedAuthenticationService {} +impl api::RefundSync for UnifiedAuthenticationService {} +impl api::PaymentToken for UnifiedAuthenticationService {} +impl api::UnifiedAuthenticationService for UnifiedAuthenticationService {} +impl api::UasPreAuthentication for UnifiedAuthenticationService {} +impl api::UasPostAuthentication for UnifiedAuthenticationService {} + +impl ConnectorIntegration + for UnifiedAuthenticationService +{ +} + +impl ConnectorIntegration + for UnifiedAuthenticationService +{ +} + +impl ConnectorCommonExt + for UnifiedAuthenticationService +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let header = vec![ + ( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + ), + ( + headers::SOURCE.to_string(), + self.get_content_type().to_string().into(), + ), + ]; + Ok(header) + } +} + +impl ConnectorCommon for UnifiedAuthenticationService { + fn id(&self) -> &'static str { + "unified_authentication_service" + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Base + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { + connectors.unified_authentication_service.base_url.as_ref() + } + + fn build_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: unified_authentication_service::UnifiedAuthenticationServiceErrorResponse = + res.response + .parse_struct("UnifiedAuthenticationServiceErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.error.clone(), + message: NO_ERROR_MESSAGE.to_owned(), + reason: Some(response.error), + attempt_status: None, + connector_transaction_id: None, + }) + } +} + +impl ConnectorValidation for UnifiedAuthenticationService { + //TODO: implement functions when support enabled +} + +impl ConnectorIntegration + for UnifiedAuthenticationService +{ + //TODO: implement sessions flow +} + +impl ConnectorIntegration + for UnifiedAuthenticationService +{ +} + +impl ConnectorIntegration + for UnifiedAuthenticationService +{ +} + +impl + ConnectorIntegration< + PreAuthenticate, + UasPreAuthenticationRequestData, + UasAuthenticationResponseData, + > for UnifiedAuthenticationService +{ + fn get_headers( + &self, + req: &UasPreAuthenticationRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &UasPreAuthenticationRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!( + "{}pre_authentication_processing", + self.base_url(connectors) + )) + } + + fn get_request_body( + &self, + req: &UasPreAuthenticationRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let transaction_details = req.request.transaction_details.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "transaction_details", + }, + )?; + let amount = utils::convert_amount( + self.amount_converter, + transaction_details.amount, + transaction_details.currency, + )?; + + let connector_router_data = + unified_authentication_service::UnifiedAuthenticationServiceRouterData::from(( + amount, req, + )); + let connector_req = + unified_authentication_service::UnifiedAuthenticationServicePreAuthenticateRequest::try_from( + &connector_router_data, + )?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &UasPreAuthenticationRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::UasPreAuthenticationType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::UasPreAuthenticationType::get_headers( + self, req, connectors, + )?) + .set_body(types::UasPreAuthenticationType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &UasPreAuthenticationRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: unified_authentication_service::UnifiedAuthenticationServicePreAuthenticateResponse = + res.response + .parse_struct("UnifiedAuthenticationService UnifiedAuthenticationServicePreAuthenticateResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl + ConnectorIntegration< + PostAuthenticate, + UasPostAuthenticationRequestData, + UasAuthenticationResponseData, + > for UnifiedAuthenticationService +{ + fn get_headers( + &self, + req: &UasPostAuthenticationRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &UasPostAuthenticationRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!( + "{}post_authentication_sync", + self.base_url(connectors) + )) + } + + fn get_request_body( + &self, + req: &UasPostAuthenticationRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = + unified_authentication_service::UnifiedAuthenticationServicePostAuthenticateRequest::try_from( + req, + )?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &UasPostAuthenticationRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::UasPostAuthenticationType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::UasPostAuthenticationType::get_headers( + self, req, connectors, + )?) + .set_body(types::UasPostAuthenticationType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &UasPostAuthenticationRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: unified_authentication_service::UnifiedAuthenticationServicePostAuthenticateResponse = + res.response + .parse_struct("UnifiedAuthenticationService UnifiedAuthenticationServicePostAuthenticateResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration + for UnifiedAuthenticationService +{ +} + +impl ConnectorIntegration + for UnifiedAuthenticationService +{ +} + +impl ConnectorIntegration + for UnifiedAuthenticationService +{ +} + +impl ConnectorIntegration + for UnifiedAuthenticationService +{ +} + +impl ConnectorIntegration + for UnifiedAuthenticationService +{ +} + +#[async_trait::async_trait] +impl webhooks::IncomingWebhook for UnifiedAuthenticationService { + fn get_webhook_object_reference_id( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_event_type( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_resource_object( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } +} + +impl ConnectorSpecifications for UnifiedAuthenticationService {} diff --git a/crates/hyperswitch_connectors/src/connectors/unified_authentication_service/transformers.rs b/crates/hyperswitch_connectors/src/connectors/unified_authentication_service/transformers.rs new file mode 100644 index 000000000000..9af21164d064 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/unified_authentication_service/transformers.rs @@ -0,0 +1,429 @@ +use common_enums::enums; +use common_utils::types::FloatMajorUnit; +use hyperswitch_domain_models::{ + router_data::{ConnectorAuthType, RouterData}, + router_request_types::unified_authentication_service::{ + DynamicData, PostAuthenticationDetails, TokenDetails, UasAuthenticationResponseData, + }, + types::{UasPostAuthenticationRouterData, UasPreAuthenticationRouterData}, +}; +use hyperswitch_interfaces::errors; +use masking::Secret; +use serde::{Deserialize, Serialize}; +use time::PrimitiveDateTime; + +use crate::types::ResponseRouterData; + +const CTP_MASTERCARD: &str = "ctp_mastercard"; + +//TODO: Fill the struct with respective fields +pub struct UnifiedAuthenticationServiceRouterData { + pub amount: FloatMajorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub router_data: T, +} + +impl From<(FloatMajorUnit, T)> for UnifiedAuthenticationServiceRouterData { + fn from((amount, item): (FloatMajorUnit, T)) -> Self { + //Todo : use utils to convert the amount to the type of amount that a connector accepts + Self { + amount, + router_data: item, + } + } +} + +#[derive(Debug, Serialize, PartialEq)] +pub struct UnifiedAuthenticationServicePreAuthenticateRequest { + pub authenticate_by: String, + pub session_id: String, + pub source_authentication_id: String, + pub authentication_info: Option, + pub service_details: Option, + pub customer_details: Option, + pub pmt_details: Option, + pub auth_creds: AuthType, + pub transaction_details: Option, +} + +#[derive(Debug, Serialize, PartialEq)] +#[serde(tag = "auth_type")] +pub enum AuthType { + HeaderKey { api_key: Secret }, +} + +#[derive(Default, Debug, Serialize, PartialEq)] +pub struct PaymentDetails { + pub pan: cards::CardNumber, + pub digital_card_id: Option, + pub payment_data_type: Option, + pub encrypted_src_card_details: Option, + pub card_expiry_date: Secret, + pub cardholder_name: Secret, + pub card_token_number: Secret, + pub account_type: u8, +} + +#[derive(Default, Debug, Serialize, PartialEq)] +pub struct TransactionDetails { + pub amount: FloatMajorUnit, + pub currency: enums::Currency, + pub date: Option, + pub pan_source: Option, + pub protection_type: Option, + pub entry_mode: Option, + pub transaction_type: Option, + pub otp_value: Option, + pub three_ds_data: Option, +} + +#[derive(Default, Debug, Serialize, PartialEq)] +pub struct ThreeDSData { + pub browser: BrowserInfo, + pub acquirer: Acquirer, +} + +#[derive(Default, Debug, Serialize, PartialEq)] +pub struct Acquirer { + pub merchant_id: String, + pub bin: u32, +} + +#[derive(Default, Debug, Serialize, PartialEq)] +pub struct BrowserInfo { + pub accept_header: String, + pub screen_width: u32, + pub screen_height: u32, + pub java_enabled: bool, + pub javascript_enabled: bool, + pub language: String, + pub user_agent: String, + pub color_depth: u32, + pub ip: String, + pub tz: i32, + pub time_zone: i8, + pub challenge_window_size: String, +} + +#[derive(Default, Debug, Serialize, PartialEq)] +pub struct AuthenticationInfo { + pub authentication_type: Option, + pub authentication_reasons: Option>, + pub consent_received: bool, + pub is_authenticated: bool, + pub locale: Option, + pub supported_card_brands: Option, +} + +#[derive(Default, Debug, Serialize, PartialEq)] +pub struct CtpServiceDetails { + pub service_session_ids: Option, + pub merchant_details: Option, +} + +#[derive(Default, Debug, Serialize, PartialEq)] +pub struct ServiceSessionIds { + pub client_id: Option, + pub service_id: Option, + pub correlation_id: Option, + pub client_reference_id: Option, + pub merchant_transaction_id: Option, + pub x_src_flow_id: Option, +} + +#[derive(Default, Debug, Serialize, PartialEq)] +pub struct MerchantDetails { + pub merchant_id: String, + pub merchant_name: String, + pub mcc: String, + pub country_code: String, + pub name: String, + pub requestor_id: String, + pub requestor_name: String, + pub configuration_id: String, + pub merchant_country: String, + pub merchant_category_code: u32, +} + +#[derive(Default, Debug, Serialize, PartialEq)] +pub struct Address { + pub city: String, + pub country: String, + pub line1: Secret, + pub line2: Secret, + pub line3: Option>, + pub post_code: Secret, + pub state: Secret, +} + +#[derive(Default, Debug, Serialize, PartialEq)] +pub struct CustomerDetails { + pub name: Secret, + pub email: Option>, + pub phone_number: Option>, + pub customer_id: String, + #[serde(rename = "type")] + pub customer_type: Option, + pub billing_address: Address, + pub shipping_address: Address, + pub wallet_account_id: Secret, + pub email_hash: Secret, + pub country_code: String, + pub national_identifier: String, +} + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct UnifiedAuthenticationServiceCard { + number: cards::CardNumber, + expiry_month: Secret, + expiry_year: Secret, + cvc: Secret, + complete: bool, +} + +impl TryFrom<&UnifiedAuthenticationServiceRouterData<&UasPreAuthenticationRouterData>> + for UnifiedAuthenticationServicePreAuthenticateRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &UnifiedAuthenticationServiceRouterData<&UasPreAuthenticationRouterData>, + ) -> Result { + let auth_type = + UnifiedAuthenticationServiceAuthType::try_from(&item.router_data.connector_auth_type)?; + let authentication_id = item.router_data.authentication_id.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "authentication_id", + }, + )?; + Ok(Self { + authenticate_by: item.router_data.connector.clone(), + session_id: authentication_id.clone(), + source_authentication_id: authentication_id, + authentication_info: None, + service_details: Some(CtpServiceDetails { + service_session_ids: item.router_data.request.service_details.clone().map( + |service_details| ServiceSessionIds { + client_id: None, + service_id: None, + correlation_id: service_details + .service_session_ids + .clone() + .and_then(|service_session_ids| service_session_ids.correlation_id), + client_reference_id: None, + merchant_transaction_id: service_details + .service_session_ids + .clone() + .and_then(|service_session_ids| { + service_session_ids.merchant_transaction_id + }), + x_src_flow_id: service_details + .service_session_ids + .clone() + .and_then(|service_session_ids| service_session_ids.x_src_flow_id), + }, + ), + merchant_details: None, + }), + customer_details: None, + pmt_details: None, + auth_creds: AuthType::HeaderKey { + api_key: auth_type.api_key, + }, + transaction_details: Some(TransactionDetails { + amount: item.amount, + currency: item + .router_data + .request + .transaction_details + .clone() + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "transaction_details", + })? + .currency, + date: None, + pan_source: None, + protection_type: None, + entry_mode: None, + transaction_type: None, + otp_value: None, + three_ds_data: None, + }), + }) + } +} + +//TODO: Fill the struct with respective fields +// Auth Struct +pub struct UnifiedAuthenticationServiceAuthType { + pub(super) api_key: Secret, +} + +impl TryFrom<&ConnectorAuthType> for UnifiedAuthenticationServiceAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &ConnectorAuthType) -> Result { + match auth_type { + ConnectorAuthType::HeaderKey { api_key } => Ok(Self { + api_key: api_key.to_owned(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum UnifiedAuthenticationServicePreAuthenticateStatus { + ACKSUCCESS, + ACKFAILURE, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct UnifiedAuthenticationServicePreAuthenticateResponse { + status: UnifiedAuthenticationServicePreAuthenticateStatus, +} + +impl + TryFrom< + ResponseRouterData< + F, + UnifiedAuthenticationServicePreAuthenticateResponse, + T, + UasAuthenticationResponseData, + >, + > for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + F, + UnifiedAuthenticationServicePreAuthenticateResponse, + T, + UasAuthenticationResponseData, + >, + ) -> Result { + Ok(Self { + response: Ok(UasAuthenticationResponseData::PreAuthentication {}), + ..item.data + }) + } +} + +#[derive(Debug, Serialize, PartialEq)] +pub struct UnifiedAuthenticationServicePostAuthenticateRequest { + pub authenticate_by: String, + pub source_authentication_id: String, + pub auth_creds: AuthType, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct UnifiedAuthenticationServicePostAuthenticateResponse { + pub authentication_details: AuthenticationDetails, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct AuthenticationDetails { + pub eci: Option, + pub token_details: UasTokenDetails, + pub dynamic_data_details: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct UasTokenDetails { + pub payment_token: cards::CardNumber, + pub payment_account_reference: String, + pub token_expiration_month: Secret, + pub token_expiration_year: Secret, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct UasDynamicData { + pub dynamic_data_value: Option>, + pub dynamic_data_type: String, + pub ds_trans_id: Option, +} + +impl TryFrom<&UasPostAuthenticationRouterData> + for UnifiedAuthenticationServicePostAuthenticateRequest +{ + type Error = error_stack::Report; + fn try_from(item: &UasPostAuthenticationRouterData) -> Result { + let auth_type = UnifiedAuthenticationServiceAuthType::try_from(&item.connector_auth_type)?; + Ok(Self { + authenticate_by: CTP_MASTERCARD.to_owned(), + source_authentication_id: item.authentication_id.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "authentication_id", + }, + )?, + auth_creds: AuthType::HeaderKey { + api_key: auth_type.api_key, + }, + }) + } +} + +impl + TryFrom< + ResponseRouterData< + F, + UnifiedAuthenticationServicePostAuthenticateResponse, + T, + UasAuthenticationResponseData, + >, + > for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + F, + UnifiedAuthenticationServicePostAuthenticateResponse, + T, + UasAuthenticationResponseData, + >, + ) -> Result { + Ok(Self { + response: Ok(UasAuthenticationResponseData::PostAuthentication { + authentication_details: PostAuthenticationDetails { + eci: item.response.authentication_details.eci, + token_details: TokenDetails { + payment_token: item + .response + .authentication_details + .token_details + .payment_token, + payment_account_reference: item + .response + .authentication_details + .token_details + .payment_account_reference, + token_expiration_month: item + .response + .authentication_details + .token_details + .token_expiration_month, + token_expiration_year: item + .response + .authentication_details + .token_details + .token_expiration_year, + }, + dynamic_data_details: item + .response + .authentication_details + .dynamic_data_details + .map(|dynamic_data| DynamicData { + dynamic_data_value: dynamic_data.dynamic_data_value, + dynamic_data_type: dynamic_data.dynamic_data_type, + ds_trans_id: dynamic_data.ds_trans_id, + }), + }, + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct UnifiedAuthenticationServiceErrorResponse { + pub error: String, +} diff --git a/crates/hyperswitch_connectors/src/connectors/volt.rs b/crates/hyperswitch_connectors/src/connectors/volt.rs index e90248970141..630218814436 100644 --- a/crates/hyperswitch_connectors/src/connectors/volt.rs +++ b/crates/hyperswitch_connectors/src/connectors/volt.rs @@ -28,7 +28,10 @@ use hyperswitch_domain_models::{ }, }; use hyperswitch_interfaces::{ - api::{self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorValidation}, + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, configs::Connectors, errors, events::connector_api_logs::ConnectorEvent, @@ -727,3 +730,5 @@ impl webhooks::IncomingWebhook for Volt { Ok(Box::new(details)) } } + +impl ConnectorSpecifications for Volt {} diff --git a/crates/hyperswitch_connectors/src/connectors/volt/transformers.rs b/crates/hyperswitch_connectors/src/connectors/volt/transformers.rs index 953b6316ff80..a0d349eaa984 100644 --- a/crates/hyperswitch_connectors/src/connectors/volt/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/volt/transformers.rs @@ -143,15 +143,19 @@ impl TryFrom<&VoltRouterData<&types::PaymentsAuthorizeRouterData>> for VoltPayme | PaymentMethodData::MandatePayment | PaymentMethodData::Reward | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) | PaymentMethodData::Upi(_) | PaymentMethodData::Voucher(_) | PaymentMethodData::GiftCard(_) | PaymentMethodData::OpenBanking(_) | PaymentMethodData::CardToken(_) - | PaymentMethodData::NetworkToken(_) => Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("Volt"), - ) - .into()), + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Volt"), + ) + .into()) + } } } } @@ -231,22 +235,27 @@ impl TryFrom<&ConnectorAuthType> for VoltAuthType { } } -impl From for enums::AttemptStatus { - fn from(item: VoltPaymentStatus) -> Self { - match item { - VoltPaymentStatus::Received | VoltPaymentStatus::Settled => Self::Charged, - VoltPaymentStatus::Completed | VoltPaymentStatus::DelayedAtBank => Self::Pending, - VoltPaymentStatus::NewPayment - | VoltPaymentStatus::BankRedirect - | VoltPaymentStatus::AwaitingCheckoutAuthorisation => Self::AuthenticationPending, - VoltPaymentStatus::RefusedByBank - | VoltPaymentStatus::RefusedByRisk - | VoltPaymentStatus::NotReceived - | VoltPaymentStatus::ErrorAtBank - | VoltPaymentStatus::CancelledByUser - | VoltPaymentStatus::AbandonedByUser - | VoltPaymentStatus::Failed => Self::Failure, +fn get_attempt_status( + (item, current_status): (VoltPaymentStatus, enums::AttemptStatus), +) -> enums::AttemptStatus { + match item { + VoltPaymentStatus::Received | VoltPaymentStatus::Settled => enums::AttemptStatus::Charged, + VoltPaymentStatus::Completed | VoltPaymentStatus::DelayedAtBank => { + enums::AttemptStatus::Pending + } + VoltPaymentStatus::NewPayment + | VoltPaymentStatus::BankRedirect + | VoltPaymentStatus::AwaitingCheckoutAuthorisation => { + enums::AttemptStatus::AuthenticationPending } + VoltPaymentStatus::RefusedByBank + | VoltPaymentStatus::RefusedByRisk + | VoltPaymentStatus::NotReceived + | VoltPaymentStatus::ErrorAtBank + | VoltPaymentStatus::CancelledByUser + | VoltPaymentStatus::AbandonedByUser + | VoltPaymentStatus::Failed => enums::AttemptStatus::Failure, + VoltPaymentStatus::Unknown => current_status, } } @@ -274,8 +283,8 @@ impl TryFrom TryFrom Result { match item.response { VoltPaymentsResponseData::PsyncResponse(payment_response) => { - let status = enums::AttemptStatus::from(payment_response.status.clone()); + let status = + get_attempt_status((payment_response.status.clone(), item.data.status)); Ok(Self { status, response: if is_payment_failure(status) { @@ -348,8 +359,8 @@ impl TryFrom TryFrom, pub status: VoltWebhookPaymentStatus, diff --git a/crates/hyperswitch_connectors/src/connectors/worldline.rs b/crates/hyperswitch_connectors/src/connectors/worldline.rs index c5a8466175c1..1bfef7b752aa 100644 --- a/crates/hyperswitch_connectors/src/connectors/worldline.rs +++ b/crates/hyperswitch_connectors/src/connectors/worldline.rs @@ -31,8 +31,8 @@ use hyperswitch_domain_models::{ }; use hyperswitch_interfaces::{ api::{ - self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorValidation, - PaymentCapture, PaymentSync, + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, PaymentCapture, PaymentSync, }, configs::Connectors, errors, @@ -41,7 +41,7 @@ use hyperswitch_interfaces::{ PaymentsAuthorizeType, PaymentsCaptureType, PaymentsSyncType, PaymentsVoidType, RefundExecuteType, RefundSyncType, Response, }, - webhooks, + webhooks::{self, IncomingWebhookFlowError}, }; use masking::{ExposeInterface, Mask, PeekInterface}; use ring::hmac; @@ -62,7 +62,7 @@ impl Worldline { pub fn generate_authorization_token( &self, auth: worldline::WorldlineAuthType, - http_method: &Method, + http_method: Method, content_type: &str, date: &str, endpoint: &str, @@ -113,7 +113,7 @@ where let date = Self::get_current_date_time()?; let content_type = Self::get_content_type(self); let signed_data: String = - self.generate_authorization_token(auth, &http_method, content_type, &date, &endpoint)?; + self.generate_authorization_token(auth, http_method, content_type, &date, &endpoint)?; Ok(vec![ (headers::DATE.to_string(), date.into()), @@ -170,14 +170,17 @@ impl ConnectorCommon for Worldline { } impl ConnectorValidation for Worldline { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( utils::construct_not_implemented_error_report(capture_method, self.id()), ), @@ -814,6 +817,7 @@ impl webhooks::IncomingWebhook for Worldline { fn get_webhook_api_response( &self, request: &webhooks::IncomingWebhookRequestDetails<'_>, + _error_kind: Option, ) -> CustomResult< hyperswitch_domain_models::api::ApplicationResponse, errors::ConnectorError, @@ -834,3 +838,5 @@ impl webhooks::IncomingWebhook for Worldline { Ok(response) } } + +impl ConnectorSpecifications for Worldline {} diff --git a/crates/hyperswitch_connectors/src/connectors/worldline/transformers.rs b/crates/hyperswitch_connectors/src/connectors/worldline/transformers.rs index 33c865c3b5f6..09d8d8aef7ee 100644 --- a/crates/hyperswitch_connectors/src/connectors/worldline/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/worldline/transformers.rs @@ -1,4 +1,3 @@ -use api_models::payments; use common_enums::enums::{AttemptStatus, BankNames, CaptureMethod, CountryAlpha2, Currency}; use common_utils::{pii::Email, request::Method}; use hyperswitch_domain_models::{ @@ -218,38 +217,41 @@ impl &RouterData, >, ) -> Result { - let payment_data = match &item.router_data.request.payment_method_data { - PaymentMethodData::Card(card) => { - let card_holder_name = item.router_data.get_optional_billing_full_name(); - WorldlinePaymentMethod::CardPaymentMethodSpecificInput(Box::new(make_card_request( - &item.router_data.request, - card, - card_holder_name, - )?)) - } - PaymentMethodData::BankRedirect(bank_redirect) => { - WorldlinePaymentMethod::RedirectPaymentMethodSpecificInput(Box::new( - make_bank_redirect_request(item.router_data, bank_redirect)?, - )) - } - PaymentMethodData::CardRedirect(_) - | PaymentMethodData::Wallet(_) - | PaymentMethodData::PayLater(_) - | PaymentMethodData::BankDebit(_) - | PaymentMethodData::BankTransfer(_) - | PaymentMethodData::Crypto(_) - | PaymentMethodData::MandatePayment - | PaymentMethodData::Reward - | PaymentMethodData::RealTimePayment(_) - | PaymentMethodData::Upi(_) - | PaymentMethodData::Voucher(_) - | PaymentMethodData::GiftCard(_) - | PaymentMethodData::OpenBanking(_) - | PaymentMethodData::CardToken(_) - | PaymentMethodData::NetworkToken(_) => Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("worldline"), - ))?, - }; + let payment_data = + match &item.router_data.request.payment_method_data { + PaymentMethodData::Card(card) => { + let card_holder_name = item.router_data.get_optional_billing_full_name(); + WorldlinePaymentMethod::CardPaymentMethodSpecificInput(Box::new( + make_card_request(&item.router_data.request, card, card_holder_name)?, + )) + } + PaymentMethodData::BankRedirect(bank_redirect) => { + WorldlinePaymentMethod::RedirectPaymentMethodSpecificInput(Box::new( + make_bank_redirect_request(item.router_data, bank_redirect)?, + )) + } + PaymentMethodData::CardRedirect(_) + | PaymentMethodData::Wallet(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("worldline"), + ))? + } + }; let billing_address = item.router_data.get_billing()?; @@ -415,15 +417,18 @@ fn make_bank_redirect_request( } fn get_address( - billing: &payments::Address, -) -> Option<(&payments::Address, &payments::AddressDetails)> { + billing: &hyperswitch_domain_models::address::Address, +) -> Option<( + &hyperswitch_domain_models::address::Address, + &hyperswitch_domain_models::address::AddressDetails, +)> { let address = billing.address.as_ref()?; address.country.as_ref()?; Some((billing, address)) } fn build_customer_info( - billing_address: &payments::Address, + billing_address: &hyperswitch_domain_models::address::Address, email: &Option, ) -> Result> { let (billing, address) = @@ -451,8 +456,8 @@ fn build_customer_info( }) } -impl From for BillingAddress { - fn from(value: payments::AddressDetails) -> Self { +impl From for BillingAddress { + fn from(value: hyperswitch_domain_models::address::AddressDetails) -> Self { Self { city: value.city, country_code: value.country, @@ -463,8 +468,8 @@ impl From for BillingAddress { } } -impl From for Shipping { - fn from(value: payments::AddressDetails) -> Self { +impl From for Shipping { + fn from(value: hyperswitch_domain_models::address::AddressDetails) -> Self { Self { city: value.city, country_code: value.country, @@ -533,12 +538,16 @@ fn get_status(item: (PaymentStatus, CaptureMethod)) -> AttemptStatus { PaymentStatus::Rejected => AttemptStatus::Failure, PaymentStatus::RejectedCapture => AttemptStatus::CaptureFailed, PaymentStatus::CaptureRequested => { - if capture_method == CaptureMethod::Automatic { + if matches!( + capture_method, + CaptureMethod::Automatic | CaptureMethod::SequentialAutomatic + ) { AttemptStatus::Pending } else { AttemptStatus::CaptureInitiated } } + PaymentStatus::PendingApproval => AttemptStatus::Authorized, PaymentStatus::Created => AttemptStatus::Started, PaymentStatus::Redirected => AttemptStatus::AuthenticationPending, @@ -568,8 +577,8 @@ impl TryFrom> status: get_status((item.response.status, item.response.capture_method)), response: Ok(PaymentsResponseData::TransactionResponse { resource_id: ResponseId::ConnectorTransactionId(item.response.id.clone()), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(item.response.id), @@ -619,8 +628,8 @@ impl TryFrom + Sync), +} + +impl Worldpay { + pub const fn new() -> &'static Self { + &Self { + amount_converter: &MinorUnitForConnector, + } + } +} + +impl ConnectorCommonExt for Worldpay +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut headers = vec![ + ( + headers::ACCEPT.to_string(), + self.get_content_type().to_string().into(), + ), + ( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + ), + (headers::WP_API_VERSION.to_string(), "2024-06-01".into()), + ]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + headers.append(&mut api_key); + Ok(headers) + } +} + +impl ConnectorCommon for Worldpay { + fn id(&self) -> &'static str { + "worldpay" + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Minor + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { + connectors.worldpay.base_url.as_ref() + } + + fn get_auth_header( + &self, + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + let auth = worldpay::WorldpayAuthType::try_from(auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + Ok(vec![( + headers::AUTHORIZATION.to_string(), + auth.api_key.into_masked(), + )]) + } + + fn build_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response = if !res.response.is_empty() { + res.response + .parse_struct("WorldpayErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)? + } else { + WorldpayErrorResponse::default(res.status_code) + }; + + event_builder.map(|i| i.set_error_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.error_name, + message: response.message, + reason: response.validation_errors.map(|e| e.to_string()), + attempt_status: Some(enums::AttemptStatus::Failure), + connector_transaction_id: None, + }) + } +} + +impl ConnectorValidation for Worldpay { + fn validate_connector_against_payment_request( + &self, + capture_method: Option, + _payment_method: enums::PaymentMethod, + _pmt: Option, + ) -> CustomResult<(), errors::ConnectorError> { + let capture_method = capture_method.unwrap_or_default(); + match capture_method { + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), + enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( + construct_not_implemented_error_report(capture_method, self.id()), + ), + } + } + + fn validate_mandate_payment( + &self, + pm_type: Option, + pm_data: PaymentMethodData, + ) -> CustomResult<(), errors::ConnectorError> { + let mandate_supported_pmd = std::collections::HashSet::from([PaymentMethodDataType::Card]); + is_mandate_supported(pm_data.clone(), pm_type, mandate_supported_pmd, self.id()) + } + + fn is_webhook_source_verification_mandatory(&self) -> bool { + true + } +} + +impl api::Payment for Worldpay {} + +impl api::MandateSetup for Worldpay {} +impl ConnectorIntegration + for Worldpay +{ + fn get_headers( + &self, + req: &SetupMandateRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &SetupMandateRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!("{}api/payments", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &SetupMandateRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let auth = worldpay::WorldpayAuthType::try_from(&req.connector_auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + let connector_router_data = worldpay::WorldpayRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.minor_amount.unwrap_or_default(), + req, + ))?; + let connector_req = + WorldpayPaymentsRequest::try_from((&connector_router_data, &auth.entity_id))?; + + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &SetupMandateRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(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: &SetupMandateRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: WorldpayPaymentsResponse = res + .response + .parse_struct("Worldpay PaymentsResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + let optional_correlation_id = res.headers.and_then(|headers| { + headers + .get(WP_CORRELATION_ID) + .and_then(|header_value| header_value.to_str().ok()) + .map(|id| id.to_string()) + }); + + RouterData::foreign_try_from(( + ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }, + optional_correlation_id, + )) + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } + + fn get_5xx_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl api::PaymentToken for Worldpay {} + +impl ConnectorIntegration + for Worldpay +{ + // Not Implemented (R) +} + +impl api::PaymentVoid for Worldpay {} + +impl ConnectorIntegration for Worldpay { + fn get_headers( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult { + let connector_payment_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}api/payments/{}/cancellations", + self.base_url(connectors), + urlencoding::encode(&connector_payment_id), + )) + } + + fn build_request( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&PaymentsVoidType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(PaymentsVoidType::get_headers(self, req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCancelRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult + where + Void: Clone, + PaymentsCancelData: Clone, + PaymentsResponseData: Clone, + { + match res.status_code { + 202 => { + let response: WorldpayPaymentsResponse = res + .response + .parse_struct("Worldpay PaymentsResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + let optional_correlation_id = res.headers.and_then(|headers| { + headers + .get(WP_CORRELATION_ID) + .and_then(|header_value| header_value.to_str().ok()) + .map(|id| id.to_string()) + }); + Ok(PaymentsCancelRouterData { + status: enums::AttemptStatus::from(response.outcome.clone()), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::foreign_try_from(( + response, + Some(data.request.connector_transaction_id.clone()), + ))?, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: optional_correlation_id, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..data.clone() + }) + } + _ => Err(errors::ConnectorError::ResponseHandlingFailed)?, + } + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl api::ConnectorAccessToken for Worldpay {} + +impl ConnectorIntegration for Worldpay {} + +impl api::PaymentSync for Worldpay {} +impl ConnectorIntegration for Worldpay { + fn get_headers( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult { + let connector_payment_id = req + .request + .connector_transaction_id + .get_connector_transaction_id() + .change_context(errors::ConnectorError::MissingConnectorTransactionID)?; + Ok(format!( + "{}api/payments/{}", + self.base_url(connectors), + urlencoding::encode(&connector_payment_id), + )) + } + + fn build_request( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .build(), + )) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response = if !res.response.is_empty() { + res.response + .parse_struct("WorldpayErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)? + } else { + WorldpayErrorResponse::default(res.status_code) + }; + + event_builder.map(|i| i.set_error_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.error_name, + message: response.message, + reason: response.validation_errors.map(|e| e.to_string()), + attempt_status: None, + connector_transaction_id: None, + }) + } + + fn handle_response( + &self, + data: &PaymentsSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: WorldpayEventResponse = + res.response + .parse_struct("Worldpay EventResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + let optional_correlation_id = res.headers.and_then(|headers| { + headers + .get(WP_CORRELATION_ID) + .and_then(|header_value| header_value.to_str().ok()) + .map(|id| id.to_string()) + }); + let attempt_status = data.status; + let worldpay_status = response.last_event; + let status = match (attempt_status, worldpay_status.clone()) { + ( + enums::AttemptStatus::Authorizing + | enums::AttemptStatus::Authorized + | enums::AttemptStatus::CaptureInitiated + | enums::AttemptStatus::Charged + | enums::AttemptStatus::Pending + | enums::AttemptStatus::VoidInitiated, + EventType::Authorized, + ) => attempt_status, + _ => enums::AttemptStatus::from(&worldpay_status), + }; + + Ok(PaymentsSyncRouterData { + status, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: data.request.connector_transaction_id.clone(), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: optional_correlation_id, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..data.clone() + }) + } +} + +impl api::PaymentCapture for Worldpay {} +impl ConnectorIntegration for Worldpay { + fn get_headers( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult { + let connector_payment_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}api/payments/{}/partialSettlements", + self.base_url(connectors), + urlencoding::encode(&connector_payment_id), + )) + } + + fn get_request_body( + &self, + req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount_to_capture = convert_amount( + self.amount_converter, + req.request.minor_amount_to_capture, + req.request.currency, + )?; + let connector_req = WorldpayPartialRequest::try_from((req, amount_to_capture))?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsCaptureType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCaptureType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCaptureRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + match res.status_code { + 202 => { + let response: WorldpayPaymentsResponse = res + .response + .parse_struct("Worldpay PaymentsResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + let optional_correlation_id = res.headers.and_then(|headers| { + headers + .get(WP_CORRELATION_ID) + .and_then(|header_value| header_value.to_str().ok()) + .map(|id| id.to_string()) + }); + Ok(PaymentsCaptureRouterData { + status: enums::AttemptStatus::from(response.outcome.clone()), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::foreign_try_from(( + response, + Some(data.request.connector_transaction_id.clone()), + ))?, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: optional_correlation_id, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..data.clone() + }) + } + _ => Err(errors::ConnectorError::ResponseHandlingFailed)?, + } + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } + + fn get_5xx_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl api::PaymentSession for Worldpay {} + +impl ConnectorIntegration for Worldpay {} + +impl api::PaymentAuthorize for Worldpay {} + +impl ConnectorIntegration for Worldpay { + fn get_headers( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!("{}api/payments", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_router_data = worldpay::WorldpayRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.minor_amount, + req, + ))?; + let auth = worldpay::WorldpayAuthType::try_from(&req.connector_auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + let connector_req = + WorldpayPaymentsRequest::try_from((&connector_router_data, &auth.entity_id))?; + + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: WorldpayPaymentsResponse = res + .response + .parse_struct("Worldpay PaymentsResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + let optional_correlation_id = res.headers.and_then(|headers| { + headers + .get(WP_CORRELATION_ID) + .and_then(|header_value| header_value.to_str().ok()) + .map(|id| id.to_string()) + }); + + RouterData::foreign_try_from(( + ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }, + optional_correlation_id, + )) + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } + + fn get_5xx_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl api::PaymentsCompleteAuthorize for Worldpay {} +impl ConnectorIntegration + for Worldpay +{ + fn get_headers( + &self, + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult { + let connector_payment_id = req + .request + .connector_transaction_id + .clone() + .ok_or(errors::ConnectorError::MissingConnectorTransactionID)?; + let stage = match req.status { + enums::AttemptStatus::DeviceDataCollectionPending => "3dsDeviceData".to_string(), + _ => "3dsChallenges".to_string(), + }; + Ok(format!( + "{}api/payments/{connector_payment_id}/{stage}", + self.base_url(connectors), + )) + } + + fn get_request_body( + &self, + req: &PaymentsCompleteAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let req_obj = WorldpayCompleteAuthorizationRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(req_obj))) + } + + fn build_request( + &self, + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsCompleteAuthorizeType::get_url( + self, req, connectors, + )?) + .headers(types::PaymentsCompleteAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(); + Ok(Some(request)) + } + + fn handle_response( + &self, + data: &PaymentsCompleteAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: WorldpayPaymentsResponse = res + .response + .parse_struct("WorldpayPaymentsResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + let optional_correlation_id = res.headers.and_then(|headers| { + headers + .get(WP_CORRELATION_ID) + .and_then(|header_value| header_value.to_str().ok()) + .map(|id| id.to_string()) + }); + RouterData::foreign_try_from(( + ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }, + optional_correlation_id, + )) + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } + + fn get_5xx_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl api::Refund for Worldpay {} +impl api::RefundExecute for Worldpay {} +impl api::RefundSync for Worldpay {} + +impl ConnectorIntegration for Worldpay { + fn get_headers( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_request_body( + &self, + req: &RefundExecuteRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount_to_refund = convert_amount( + self.amount_converter, + req.request.minor_refund_amount, + req.request.currency, + )?; + let connector_req = WorldpayPartialRequest::try_from((req, amount_to_refund))?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn get_url( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult { + let connector_payment_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}api/payments/{}/partialRefunds", + self.base_url(connectors), + urlencoding::encode(&connector_payment_id), + )) + } + + fn build_request( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundExecuteType::get_headers( + self, req, connectors, + )?) + .set_body(types::RefundExecuteType::get_request_body( + self, req, connectors, + )?) + .build(); + Ok(Some(request)) + } + + fn handle_response( + &self, + data: &RefundsRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult, errors::ConnectorError> { + match res.status_code { + 202 => { + let response: WorldpayPaymentsResponse = res + .response + .parse_struct("Worldpay PaymentsResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + let optional_correlation_id = res.headers.and_then(|headers| { + headers + .get(WP_CORRELATION_ID) + .and_then(|header_value| header_value.to_str().ok()) + .map(|id| id.to_string()) + }); + Ok(RefundExecuteRouterData { + response: Ok(RefundsResponseData { + refund_status: enums::RefundStatus::from(response.outcome.clone()), + connector_refund_id: ResponseIdStr::foreign_try_from(( + response, + optional_correlation_id, + ))? + .id, + }), + ..data.clone() + }) + } + _ => Err(errors::ConnectorError::ResponseHandlingFailed)?, + } + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } + + fn get_5xx_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Worldpay { + fn get_headers( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!( + "{}api/payments/{}", + self.base_url(connectors), + urlencoding::encode(&req.request.get_connector_refund_id()?), + )) + } + + fn build_request( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::RefundSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundSyncType::get_headers(self, req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &RefundSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: WorldpayEventResponse = + res.response + .parse_struct("Worldpay EventResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + Ok(RefundSyncRouterData { + response: Ok(RefundsResponseData { + connector_refund_id: data.request.refund_id.clone(), + refund_status: enums::RefundStatus::from(response.last_event), + }), + ..data.clone() + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } + + fn get_5xx_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +#[async_trait::async_trait] +impl IncomingWebhook for Worldpay { + fn get_webhook_source_verification_algorithm( + &self, + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Ok(Box::new(crypto::Sha256)) + } + + fn get_webhook_source_verification_signature( + &self, + request: &IncomingWebhookRequestDetails<'_>, + _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, + ) -> CustomResult, errors::ConnectorError> { + let event_signature = get_header_key_value("Event-Signature", request.headers)?.split(','); + let sign_header = event_signature + .last() + .ok_or(errors::ConnectorError::WebhookSignatureNotFound)?; + let signature = sign_header + .split('/') + .last() + .ok_or(errors::ConnectorError::WebhookSignatureNotFound)?; + hex::decode(signature).change_context(errors::ConnectorError::WebhookResponseEncodingFailed) + } + + fn get_webhook_source_verification_message( + &self, + request: &IncomingWebhookRequestDetails<'_>, + _merchant_id: &common_utils::id_type::MerchantId, + _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, + ) -> CustomResult, errors::ConnectorError> { + Ok(request.body.to_vec()) + } + + async fn verify_webhook_source( + &self, + request: &IncomingWebhookRequestDetails<'_>, + merchant_id: &common_utils::id_type::MerchantId, + connector_webhook_details: Option, + _connector_account_details: crypto::Encryptable>, + connector_label: &str, + ) -> CustomResult { + let connector_webhook_secrets = self + .get_webhook_source_verification_merchant_secret( + merchant_id, + connector_label, + connector_webhook_details, + ) + .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_id, + &connector_webhook_secrets, + ) + .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?; + let secret_key = hex::decode(connector_webhook_secrets.secret) + .change_context(errors::ConnectorError::WebhookVerificationSecretInvalid)?; + + let signing_key = hmac::Key::new(hmac::HMAC_SHA256, &secret_key); + let signed_message = hmac::sign(&signing_key, &message); + let computed_signature = hex::encode(signed_message.as_ref()); + + Ok(computed_signature.as_bytes() == hex::encode(signature).as_bytes()) + } + + fn get_webhook_object_reference_id( + &self, + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + let body: WorldpayWebhookTransactionId = request + .body + .parse_struct("WorldpayWebhookTransactionId") + .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; + Ok(api_models::webhooks::ObjectReferenceId::PaymentId( + PaymentIdType::PaymentAttemptId(body.event_details.transaction_reference), + )) + } + + fn get_webhook_event_type( + &self, + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + let body: WorldpayWebhookEventType = request + .body + .parse_struct("WorldpayWebhookEventType") + .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; + match body.event_details.event_type { + EventType::Authorized => Ok(IncomingWebhookEvent::PaymentIntentAuthorizationSuccess), + EventType::Settled => Ok(IncomingWebhookEvent::PaymentIntentSuccess), + EventType::SentForSettlement | EventType::SentForAuthorization => { + Ok(IncomingWebhookEvent::PaymentIntentProcessing) + } + EventType::Error | EventType::Expired | EventType::SettlementFailed => { + Ok(IncomingWebhookEvent::PaymentIntentFailure) + } + EventType::Unknown + | EventType::Cancelled + | EventType::Refused + | EventType::Refunded + | EventType::SentForRefund + | EventType::RefundFailed => Ok(IncomingWebhookEvent::EventNotSupported), + } + } + + fn get_webhook_resource_object( + &self, + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + let body: WorldpayWebhookEventType = request + .body + .parse_struct("WorldpayWebhookEventType") + .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?; + let psync_body = WorldpayEventResponse::try_from(body)?; + Ok(Box::new(psync_body)) + } +} + +impl ConnectorRedirectResponse for Worldpay { + fn get_flow_type( + &self, + _query_params: &str, + _json_payload: Option, + action: PaymentAction, + ) -> CustomResult { + match action { + PaymentAction::CompleteAuthorize => Ok(enums::CallConnectorAction::Trigger), + PaymentAction::PSync | PaymentAction::PaymentAuthenticateCompleteAuthorize => { + Ok(enums::CallConnectorAction::Avoid) + } + } + } +} + +impl ConnectorSpecifications for Worldpay {} diff --git a/crates/router/src/connector/worldpay/requests.rs b/crates/hyperswitch_connectors/src/connectors/worldpay/requests.rs similarity index 65% rename from crates/router/src/connector/worldpay/requests.rs rename to crates/hyperswitch_connectors/src/connectors/worldpay/requests.rs index 5fb5d75d940b..884caa9e840b 100644 --- a/crates/router/src/connector/worldpay/requests.rs +++ b/crates/hyperswitch_connectors/src/connectors/worldpay/requests.rs @@ -1,32 +1,154 @@ use masking::Secret; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] + +#[derive(Clone, Debug, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct BillingAddress { +pub struct WorldpayPaymentsRequest { + pub transaction_reference: String, + pub merchant: Merchant, + pub instruction: Instruction, #[serde(skip_serializing_if = "Option::is_none")] - pub city: Option, + pub customer: Option, +} + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Merchant { + pub entity: Secret, #[serde(skip_serializing_if = "Option::is_none")] - pub address2: Option>, - pub postal_code: Secret, + pub mcc: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub state: Option, + pub payment_facilitator: Option, +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Instruction { #[serde(skip_serializing_if = "Option::is_none")] - pub address3: Option>, - pub country_code: common_enums::CountryAlpha2, + pub settlement: Option, + pub method: PaymentMethod, + pub payment_instrument: PaymentInstrument, + pub narrative: InstructionNarrative, + pub value: PaymentValue, #[serde(skip_serializing_if = "Option::is_none")] - pub address1: Option>, + pub debt_repayment: Option, + #[serde(rename = "threeDS")] + pub three_ds: Option, + /// For setting up mandates + pub token_creation: Option, + /// For specifying CIT vs MIT + pub customer_agreement: Option, +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +pub struct TokenCreation { + #[serde(rename = "type")] + pub token_type: TokenCreationType, +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum TokenCreationType { + Worldpay, +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CustomerAgreement { + #[serde(rename = "type")] + pub agreement_type: CustomerAgreementType, + pub stored_card_usage: StoredCardUsageType, +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum CustomerAgreementType { + Subscription, +} + +#[derive(Clone, Debug, PartialEq, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum StoredCardUsageType { + First, + Subsequent, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum PaymentInstrument { + Card(CardPayment), + CardToken(CardToken), + Googlepay(WalletPayment), + Applepay(WalletPayment), +} + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct WorldpayPaymentsRequest { +pub struct CardPayment { + #[serde(rename = "type")] + pub payment_type: PaymentType, #[serde(skip_serializing_if = "Option::is_none")] - pub channel: Option, - pub instruction: Instruction, + pub card_holder_name: Option>, + pub card_number: cards::CardNumber, + pub expiry_date: ExpiryDate, #[serde(skip_serializing_if = "Option::is_none")] - pub customer: Option, - pub merchant: Merchant, - pub transaction_reference: String, + pub billing_address: Option, + pub cvc: Secret, +} + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CardToken { + #[serde(rename = "type")] + pub payment_type: PaymentType, + pub href: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub cvc: Option>, +} + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WalletPayment { + #[serde(rename = "type")] + pub payment_type: PaymentType, + pub wallet_token: Secret, + #[serde(skip_serializing_if = "Option::is_none")] + pub billing_address: Option, +} + +#[derive( + Clone, Copy, Debug, Eq, Default, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, +)] +#[serde(rename_all = "lowercase")] +pub enum PaymentType { + #[default] + Plain, + Token, + Encrypted, + Checkout, +} + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +pub struct ExpiryDate { + pub month: Secret, + pub year: Secret, +} + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BillingAddress { + #[serde(skip_serializing_if = "Option::is_none")] + pub address1: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub address2: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub address3: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub city: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub state: Option>, + pub postal_code: Secret, + pub country_code: common_enums::CountryAlpha2, } #[derive( @@ -35,6 +157,7 @@ pub struct WorldpayPaymentsRequest { #[serde(rename_all = "camelCase")] pub enum Channel { #[default] + Ecom, Moto, } @@ -100,99 +223,75 @@ pub struct NetworkToken { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct Instruction { - #[serde(skip_serializing_if = "Option::is_none")] - pub debt_repayment: Option, - pub value: PaymentValue, - pub narrative: InstructionNarrative, - pub payment_instrument: PaymentInstrument, +pub struct AutoSettlement { + pub auto: bool, } -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct InstructionNarrative { - pub line1: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub line2: Option, +pub struct ThreeDSRequest { + #[serde(rename = "type")] + pub three_ds_type: String, + pub mode: String, + pub device_data: ThreeDSRequestDeviceData, + pub challenge: ThreeDSRequestChallenge, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] -pub enum PaymentInstrument { - Card(CardPayment), - CardToken(CardToken), - Googlepay(WalletPayment), - Applepay(WalletPayment), +#[serde(rename_all = "camelCase")] +pub struct ThreeDSRequestDeviceData { + pub accept_header: String, + pub user_agent_header: String, + pub browser_language: Option, + pub browser_screen_width: Option, + pub browser_screen_height: Option, + pub browser_color_depth: Option, + pub time_zone: Option, + pub browser_java_enabled: Option, + pub browser_javascript_enabled: Option, + pub channel: Option, } -#[derive( - Clone, Copy, Debug, Eq, Default, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, -)] -pub enum PaymentType { - #[default] - #[serde(rename = "card/plain")] - Card, - #[serde(rename = "card/token")] - CardToken, - #[serde(rename = "card/wallet+googlepay")] - Googlepay, - #[serde(rename = "card/wallet+applepay")] - Applepay, +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum ThreeDSRequestChannel { + Browser, + Native, } -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CardPayment { - #[serde(skip_serializing_if = "Option::is_none")] - pub billing_address: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub card_holder_name: Option>, - pub card_expiry_date: CardExpiryDate, +pub struct ThreeDSRequestChallenge { + pub return_url: String, #[serde(skip_serializing_if = "Option::is_none")] - pub cvc: Option>, - #[serde(rename = "type")] - pub payment_type: PaymentType, - pub card_number: cards::CardNumber, + pub preference: Option, } -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct CardToken { - #[serde(rename = "type")] - pub payment_type: PaymentType, - pub href: String, +pub enum ThreeDsPreference { + ChallengeMandated, } #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct WalletPayment { - #[serde(rename = "type")] - pub payment_type: PaymentType, - pub wallet_token: Secret, - #[serde(skip_serializing_if = "Option::is_none")] - pub billing_address: Option, +#[serde(rename_all = "lowercase")] +pub enum PaymentMethod { + #[default] + Card, + ApplePay, + GooglePay, } #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -pub struct CardExpiryDate { - pub month: Secret, - pub year: Secret, +#[serde(rename_all = "camelCase")] +pub struct InstructionNarrative { + pub line1: String, } #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] pub struct PaymentValue { pub amount: i64, - pub currency: String, -} - -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Merchant { - pub entity: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub mcc: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub payment_facilitator: Option, + pub currency: api_models::enums::Currency, } #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] @@ -220,7 +319,17 @@ pub struct SubMerchant { } #[derive(Default, Debug, Serialize)] -pub struct WorldpayRefundRequest { +pub struct WorldpayPartialRequest { pub value: PaymentValue, pub reference: String, } + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WorldpayCompleteAuthorizationRequest { + #[serde(skip_serializing_if = "Option::is_none")] + pub collection_reference: Option, +} + +pub(super) const THREE_DS_MODE: &str = "always"; +pub(super) const THREE_DS_TYPE: &str = "integrated"; diff --git a/crates/hyperswitch_connectors/src/connectors/worldpay/response.rs b/crates/hyperswitch_connectors/src/connectors/worldpay/response.rs new file mode 100644 index 000000000000..5e9fb0304243 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/worldpay/response.rs @@ -0,0 +1,460 @@ +use error_stack::ResultExt; +use hyperswitch_interfaces::errors; +use masking::Secret; +use serde::{Deserialize, Serialize}; +use url::Url; + +use super::requests::*; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WorldpayPaymentsResponse { + pub outcome: PaymentOutcome, + pub transaction_reference: Option, + #[serde(flatten)] + pub other_fields: Option, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] +pub enum WorldpayPaymentResponseFields { + AuthorizedResponse(Box), + DDCResponse(DDCResponse), + FraudHighRisk(FraudHighRiskResponse), + RefusedResponse(RefusedResponse), + ThreeDsChallenged(ThreeDsChallengedResponse), +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AuthorizedResponse { + pub payment_instrument: PaymentsResPaymentInstrument, + #[serde(skip_serializing_if = "Option::is_none")] + pub issuer: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub scheme: Option, + #[serde(rename = "_links", skip_serializing_if = "Option::is_none")] + pub links: Option, + #[serde(rename = "_actions")] + pub actions: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + pub risk_factors: Option>, + pub fraud: Option, + /// Mandate's token + pub token: Option, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MandateToken { + pub href: Secret, + pub token_id: String, + pub token_expiry_date_time: String, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct FraudHighRiskResponse { + pub score: f32, + pub reason: Vec, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RefusedResponse { + pub refusal_description: String, + pub refusal_code: String, + pub risk_factors: Option>, + pub fraud: Option, + #[serde(rename = "threeDS")] + pub three_ds: Option, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ThreeDsResponse { + pub outcome: String, + pub issuer_response: IssuerResponse, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ThreeDsChallengedResponse { + pub authentication: AuthenticationResponse, + pub challenge: ThreeDsChallenge, + #[serde(rename = "_actions")] + pub actions: CompleteThreeDsActionLink, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct AuthenticationResponse { + pub version: String, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ThreeDsChallenge { + pub reference: String, + pub url: Url, + pub jwt: Secret, + pub payload: Secret, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct CompleteThreeDsActionLink { + #[serde(rename = "complete3dsChallenge")] + pub complete_three_ds_challenge: ActionLink, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum IssuerResponse { + Challenged, + Frictionless, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DDCResponse { + pub device_data_collection: DDCToken, + #[serde(rename = "_actions")] + pub actions: DDCActionLink, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct DDCToken { + pub jwt: Secret, + pub url: Url, + pub bin: Secret, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct DDCActionLink { + #[serde(rename = "supply3dsDeviceData")] + supply_ddc_data: ActionLink, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum PaymentOutcome { + #[serde(alias = "authorized", alias = "Authorized")] + Authorized, + Refused, + SentForSettlement, + SentForRefund, + FraudHighRisk, + #[serde(alias = "3dsDeviceDataRequired")] + ThreeDsDeviceDataRequired, + SentForCancellation, + #[serde(alias = "3dsAuthenticationFailed")] + ThreeDsAuthenticationFailed, + SentForPartialRefund, + #[serde(alias = "3dsChallenged")] + ThreeDsChallenged, + #[serde(alias = "3dsUnavailable")] + ThreeDsUnavailable, +} + +impl std::fmt::Display for PaymentOutcome { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Authorized => write!(f, "authorized"), + Self::Refused => write!(f, "refused"), + Self::SentForSettlement => write!(f, "sentForSettlement"), + Self::SentForRefund => write!(f, "sentForRefund"), + Self::FraudHighRisk => write!(f, "fraudHighRisk"), + Self::ThreeDsDeviceDataRequired => write!(f, "3dsDeviceDataRequired"), + Self::SentForCancellation => write!(f, "sentForCancellation"), + Self::ThreeDsAuthenticationFailed => write!(f, "3dsAuthenticationFailed"), + Self::SentForPartialRefund => write!(f, "sentForPartialRefund"), + Self::ThreeDsChallenged => write!(f, "3dsChallenged"), + Self::ThreeDsUnavailable => write!(f, "3dsUnavailable"), + } + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct SelfLink { + #[serde(rename = "self")] + pub self_link: SelfLinkInner, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct SelfLinkInner { + pub href: String, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ActionLinks { + supply_3ds_device_data: Option, + settle_payment: Option, + partially_settle_payment: Option, + refund_payment: Option, + partiall_refund_payment: Option, + cancel_payment: Option, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ActionLink { + pub href: String, + pub method: String, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Fraud { + pub outcome: FraudOutcome, + pub score: f32, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum FraudOutcome { + LowRisk, + HighRisk, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WorldpayEventResponse { + pub last_event: EventType, + #[serde(rename = "_links", skip_serializing_if = "Option::is_none")] + pub links: Option, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum EventType { + SentForAuthorization, + #[serde(alias = "Authorized")] + Authorized, + #[serde(alias = "Sent for Settlement")] + SentForSettlement, + Settled, + SettlementFailed, + Cancelled, + Error, + Expired, + Refused, + #[serde(alias = "Sent for Refund")] + SentForRefund, + Refunded, + RefundFailed, + #[serde(other)] + Unknown, +} + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +pub struct EventLinks { + #[serde(rename = "payments:events", skip_serializing_if = "Option::is_none")] + pub events: Option, +} + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +pub struct PaymentLink { + pub href: String, +} + +pub fn get_resource_id( + response: WorldpayPaymentsResponse, + connector_transaction_id: Option, + transform_fn: F, +) -> Result> +where + F: Fn(String) -> T, +{ + let optional_reference_id = response + .other_fields + .as_ref() + .and_then(|other_fields| match other_fields { + WorldpayPaymentResponseFields::AuthorizedResponse(res) => res + .links + .as_ref() + .and_then(|link| link.self_link.href.rsplit_once('/').map(|(_, h)| h)), + WorldpayPaymentResponseFields::DDCResponse(res) => { + res.actions.supply_ddc_data.href.split('/').nth_back(1) + } + WorldpayPaymentResponseFields::ThreeDsChallenged(res) => res + .actions + .complete_three_ds_challenge + .href + .split('/') + .nth_back(1), + WorldpayPaymentResponseFields::FraudHighRisk(_) + | WorldpayPaymentResponseFields::RefusedResponse(_) => None, + }) + .map(|href| { + urlencoding::decode(href) + .map(|s| transform_fn(s.into_owned())) + .change_context(errors::ConnectorError::ResponseHandlingFailed) + }) + .transpose()?; + optional_reference_id + .or_else(|| connector_transaction_id.map(transform_fn)) + .ok_or_else(|| { + errors::ConnectorError::MissingRequiredField { + field_name: "_links.self.href", + } + .into() + }) +} + +pub struct ResponseIdStr { + pub id: String, +} + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Issuer { + pub authorization_code: Secret, +} + +impl Issuer { + pub fn new(code: String) -> Self { + Self { + authorization_code: Secret::new(code), + } + } +} + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PaymentsResPaymentInstrument { + #[serde(rename = "type")] + pub payment_instrument_type: String, + pub card_bin: Option, + pub last_four: Option, + pub expiry_date: Option, + pub card_brand: Option, + pub funding_type: Option, + pub category: Option, + pub issuer_name: Option, + pub payment_account_reference: Option, +} + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RiskFactorsInner { + #[serde(rename = "type")] + pub risk_type: RiskType, + #[serde(skip_serializing_if = "Option::is_none")] + pub detail: Option, + pub risk: Risk, +} + +impl RiskFactorsInner { + pub fn new(risk_type: RiskType, risk: Risk) -> Self { + Self { + risk_type, + detail: None, + risk, + } + } +} + +#[derive( + Clone, Copy, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, +)] +#[serde(rename_all = "camelCase")] +pub enum RiskType { + #[default] + Avs, + Cvc, + RiskProfile, +} + +#[derive( + Clone, Copy, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, +)] +#[serde(rename_all = "lowercase")] +pub enum Detail { + #[default] + Address, + Postcode, +} + +#[derive( + Clone, Copy, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, +)] +#[serde(rename_all = "camelCase")] +pub enum Risk { + #[default] + NotChecked, + NotMatched, + NotSupplied, + VerificationFailed, +} + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +pub struct PaymentsResponseScheme { + pub reference: String, +} + +impl PaymentsResponseScheme { + pub fn new(reference: String) -> Self { + Self { reference } + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct WorldpayErrorResponse { + pub error_name: String, + pub message: String, + pub validation_errors: Option, +} + +impl WorldpayErrorResponse { + pub fn default(status_code: u16) -> Self { + match status_code { + code @ 404 => Self { + error_name: format!("{} Not found", code), + message: "Resource not found".to_string(), + validation_errors: None, + }, + code => Self { + error_name: code.to_string(), + message: "Unknown error".to_string(), + validation_errors: None, + }, + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WorldpayWebhookTransactionId { + pub event_details: EventDetails, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct EventDetails { + pub transaction_reference: String, + #[serde(rename = "type")] + pub event_type: EventType, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct WorldpayWebhookEventType { + pub event_id: String, + pub event_timestamp: String, + pub event_details: EventDetails, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub enum WorldpayWebhookStatus { + SentForSettlement, + Authorized, + SentForAuthorization, + Cancelled, + Error, + Expired, + Refused, + SentForRefund, + RefundFailed, +} + +/// Worldpay's unique reference ID for a request +pub(super) const WP_CORRELATION_ID: &str = "WP-CorrelationId"; diff --git a/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs new file mode 100644 index 000000000000..ec23b520a200 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs @@ -0,0 +1,827 @@ +use std::collections::HashMap; + +use api_models::payments::{MandateIds, MandateReferenceId}; +use base64::Engine; +use common_enums::enums; +use common_utils::{ + consts::BASE64_ENGINE, errors::CustomResult, ext_traits::OptionExt, pii, types::MinorUnit, +}; +use error_stack::ResultExt; +use hyperswitch_domain_models::{ + address, + payment_method_data::{PaymentMethodData, WalletData}, + router_data::{ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{Authorize, SetupMandate}, + router_request_types::{ + BrowserInformation, PaymentsAuthorizeData, ResponseId, SetupMandateRequestData, + }, + router_response_types::{MandateReference, PaymentsResponseData, RedirectForm}, + types, +}; +use hyperswitch_interfaces::{api, errors}; +use masking::{ExposeInterface, PeekInterface, Secret}; +use serde::{Deserialize, Serialize}; + +use super::{requests::*, response::*}; +use crate::{ + types::ResponseRouterData, + utils::{ + self, AddressData, CardData, ForeignTryFrom, PaymentsAuthorizeRequestData, + PaymentsSetupMandateRequestData, RouterData as RouterDataTrait, + }, +}; + +#[derive(Debug, Serialize)] +pub struct WorldpayRouterData { + amount: i64, + router_data: T, +} +impl TryFrom<(&api::CurrencyUnit, enums::Currency, MinorUnit, T)> for WorldpayRouterData { + type Error = error_stack::Report; + fn try_from( + (_currency_unit, _currency, minor_amount, item): ( + &api::CurrencyUnit, + enums::Currency, + MinorUnit, + T, + ), + ) -> Result { + Ok(Self { + amount: minor_amount.get_amount_as_i64(), + router_data: item, + }) + } +} + +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct WorldpayConnectorMetadataObject { + pub merchant_name: Option>, +} + +impl TryFrom> for WorldpayConnectorMetadataObject { + type Error = error_stack::Report; + fn try_from(meta_data: Option<&pii::SecretSerdeValue>) -> Result { + let metadata: Self = utils::to_connector_meta_from_secret::(meta_data.cloned()) + .change_context(errors::ConnectorError::InvalidConnectorConfig { + config: "metadata", + })?; + Ok(metadata) + } +} + +fn fetch_payment_instrument( + payment_method: PaymentMethodData, + billing_address: Option<&address::Address>, + mandate_ids: Option, +) -> CustomResult { + match payment_method { + PaymentMethodData::Card(card) => Ok(PaymentInstrument::Card(CardPayment { + payment_type: PaymentType::Plain, + expiry_date: ExpiryDate { + month: card.get_expiry_month_as_i8()?, + year: card.get_expiry_year_as_4_digit_i32()?, + }, + card_number: card.card_number, + cvc: card.card_cvc, + card_holder_name: billing_address.and_then(|address| address.get_optional_full_name()), + billing_address: if let Some(address) = + billing_address.and_then(|addr| addr.address.clone()) + { + Some(BillingAddress { + address1: address.line1, + address2: address.line2, + address3: address.line3, + city: address.city, + state: address.state, + postal_code: address.zip.get_required_value("zip").change_context( + errors::ConnectorError::MissingRequiredField { field_name: "zip" }, + )?, + country_code: address + .country + .get_required_value("country_code") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "country_code", + })?, + }) + } else { + None + }, + })), + PaymentMethodData::MandatePayment => mandate_ids + .and_then(|mandate_ids| { + mandate_ids + .mandate_reference_id + .and_then(|mandate_id| match mandate_id { + MandateReferenceId::ConnectorMandateId(connector_mandate_id) => { + connector_mandate_id.get_connector_mandate_id().map(|href| { + PaymentInstrument::CardToken(CardToken { + payment_type: PaymentType::Token, + href, + cvc: None, + }) + }) + } + _ => None, + }) + }) + .ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "connector_mandate_id", + } + .into(), + ), + PaymentMethodData::Wallet(wallet) => match wallet { + WalletData::GooglePay(data) => Ok(PaymentInstrument::Googlepay(WalletPayment { + payment_type: PaymentType::Encrypted, + wallet_token: Secret::new(data.tokenization_data.token), + ..WalletPayment::default() + })), + WalletData::ApplePay(data) => Ok(PaymentInstrument::Applepay(WalletPayment { + payment_type: PaymentType::Encrypted, + wallet_token: Secret::new(data.payment_data), + ..WalletPayment::default() + })), + WalletData::AliPayQr(_) + | WalletData::AliPayRedirect(_) + | WalletData::AliPayHkRedirect(_) + | WalletData::MomoRedirect(_) + | WalletData::KakaoPayRedirect(_) + | WalletData::GoPayRedirect(_) + | WalletData::GcashRedirect(_) + | WalletData::ApplePayRedirect(_) + | WalletData::ApplePayThirdPartySdk(_) + | WalletData::DanaRedirect {} + | WalletData::GooglePayRedirect(_) + | WalletData::GooglePayThirdPartySdk(_) + | WalletData::MbWayRedirect(_) + | WalletData::MobilePayRedirect(_) + | WalletData::PaypalRedirect(_) + | WalletData::PaypalSdk(_) + | WalletData::Paze(_) + | WalletData::SamsungPay(_) + | WalletData::TwintRedirect {} + | WalletData::VippsRedirect {} + | WalletData::TouchNGoRedirect(_) + | WalletData::WeChatPayRedirect(_) + | WalletData::CashappQr(_) + | WalletData::SwishQr(_) + | WalletData::WeChatPayQr(_) + | WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("worldpay"), + ) + .into()), + }, + PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::CardRedirect(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("worldpay"), + ) + .into()) + } + } +} + +impl TryFrom<(enums::PaymentMethod, Option)> for PaymentMethod { + type Error = error_stack::Report; + fn try_from( + src: (enums::PaymentMethod, Option), + ) -> Result { + match (src.0, src.1) { + (enums::PaymentMethod::Card, _) => Ok(Self::Card), + (enums::PaymentMethod::Wallet, pmt) => { + let pm = pmt.ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "payment_method_type", + })?; + match pm { + enums::PaymentMethodType::ApplePay => Ok(Self::ApplePay), + enums::PaymentMethodType::GooglePay => Ok(Self::GooglePay), + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("worldpay"), + ) + .into()), + } + } + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("worldpay"), + ) + .into()), + } + } +} + +// Trait to abstract common functionality between Authorize and SetupMandate +trait WorldpayPaymentsRequestData { + fn get_return_url(&self) -> Result>; + fn get_auth_type(&self) -> &enums::AuthenticationType; + fn get_browser_info(&self) -> Option<&BrowserInformation>; + fn get_payment_method_data(&self) -> &PaymentMethodData; + fn get_setup_future_usage(&self) -> Option; + fn get_off_session(&self) -> Option; + fn get_mandate_id(&self) -> Option; + fn get_currency(&self) -> enums::Currency; + fn get_optional_billing_address(&self) -> Option<&address::Address>; + fn get_connector_meta_data(&self) -> Option<&pii::SecretSerdeValue>; + fn get_payment_method(&self) -> enums::PaymentMethod; + fn get_payment_method_type(&self) -> Option; + fn get_connector_request_reference_id(&self) -> String; + fn get_is_mandate_payment(&self) -> bool; + fn get_settlement_info(&self, _amount: i64) -> Option { + None + } +} + +impl WorldpayPaymentsRequestData + for RouterData +{ + fn get_return_url(&self) -> Result> { + self.request.get_router_return_url() + } + + fn get_auth_type(&self) -> &enums::AuthenticationType { + &self.auth_type + } + + fn get_browser_info(&self) -> Option<&BrowserInformation> { + self.request.browser_info.as_ref() + } + + fn get_payment_method_data(&self) -> &PaymentMethodData { + &self.request.payment_method_data + } + + fn get_setup_future_usage(&self) -> Option { + self.request.setup_future_usage + } + + fn get_off_session(&self) -> Option { + self.request.off_session + } + + fn get_mandate_id(&self) -> Option { + self.request.mandate_id.clone() + } + + fn get_currency(&self) -> enums::Currency { + self.request.currency + } + + fn get_optional_billing_address(&self) -> Option<&address::Address> { + self.get_optional_billing() + } + + fn get_connector_meta_data(&self) -> Option<&pii::SecretSerdeValue> { + self.connector_meta_data.as_ref() + } + + fn get_payment_method(&self) -> enums::PaymentMethod { + self.payment_method + } + + fn get_payment_method_type(&self) -> Option { + self.request.payment_method_type + } + + fn get_connector_request_reference_id(&self) -> String { + self.connector_request_reference_id.clone() + } + + fn get_is_mandate_payment(&self) -> bool { + true + } +} + +impl WorldpayPaymentsRequestData + for RouterData +{ + fn get_return_url(&self) -> Result> { + self.request.get_complete_authorize_url() + } + + fn get_auth_type(&self) -> &enums::AuthenticationType { + &self.auth_type + } + + fn get_browser_info(&self) -> Option<&BrowserInformation> { + self.request.browser_info.as_ref() + } + + fn get_payment_method_data(&self) -> &PaymentMethodData { + &self.request.payment_method_data + } + + fn get_setup_future_usage(&self) -> Option { + self.request.setup_future_usage + } + + fn get_off_session(&self) -> Option { + self.request.off_session + } + + fn get_mandate_id(&self) -> Option { + self.request.mandate_id.clone() + } + + fn get_currency(&self) -> enums::Currency { + self.request.currency + } + + fn get_optional_billing_address(&self) -> Option<&address::Address> { + self.get_optional_billing() + } + + fn get_connector_meta_data(&self) -> Option<&pii::SecretSerdeValue> { + self.connector_meta_data.as_ref() + } + + fn get_payment_method(&self) -> enums::PaymentMethod { + self.payment_method + } + + fn get_payment_method_type(&self) -> Option { + self.request.payment_method_type + } + + fn get_connector_request_reference_id(&self) -> String { + self.connector_request_reference_id.clone() + } + + fn get_is_mandate_payment(&self) -> bool { + self.request.is_mandate_payment() + } + + fn get_settlement_info(&self, amount: i64) -> Option { + match (self.request.capture_method.unwrap_or_default(), amount) { + (_, 0) => None, + (enums::CaptureMethod::Automatic, _) + | (enums::CaptureMethod::SequentialAutomatic, _) => Some(AutoSettlement { auto: true }), + (enums::CaptureMethod::Manual, _) | (enums::CaptureMethod::ManualMultiple, _) => { + Some(AutoSettlement { auto: false }) + } + _ => None, + } + } +} + +// Dangling helper function to create ThreeDS request +fn create_three_ds_request( + router_data: &T, + is_mandate_payment: bool, +) -> Result, error_stack::Report> { + match router_data.get_auth_type() { + enums::AuthenticationType::ThreeDs => { + let browser_info = router_data.get_browser_info().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "browser_info", + }, + )?; + + let accept_header = browser_info + .accept_header + .clone() + .get_required_value("accept_header") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "accept_header", + })?; + + let user_agent_header = browser_info + .user_agent + .clone() + .get_required_value("user_agent") + .change_context(errors::ConnectorError::MissingRequiredField { + field_name: "user_agent", + })?; + + Ok(Some(ThreeDSRequest { + three_ds_type: THREE_DS_TYPE.to_string(), + mode: THREE_DS_MODE.to_string(), + device_data: ThreeDSRequestDeviceData { + accept_header, + user_agent_header, + browser_language: browser_info.language.clone(), + browser_screen_width: browser_info.screen_width, + browser_screen_height: browser_info.screen_height, + browser_color_depth: browser_info.color_depth.map(|depth| depth.to_string()), + time_zone: browser_info.time_zone.map(|tz| tz.to_string()), + browser_java_enabled: browser_info.java_enabled, + browser_javascript_enabled: browser_info.java_script_enabled, + channel: Some(ThreeDSRequestChannel::Browser), + }, + challenge: ThreeDSRequestChallenge { + return_url: router_data.get_return_url()?, + preference: if is_mandate_payment { + Some(ThreeDsPreference::ChallengeMandated) + } else { + None + }, + }, + })) + } + _ => Ok(None), + } +} + +// Dangling helper function to determine token and agreement settings +fn get_token_and_agreement( + payment_method_data: &PaymentMethodData, + setup_future_usage: Option, + off_session: Option, +) -> (Option, Option) { + match (payment_method_data, setup_future_usage, off_session) { + // CIT + (PaymentMethodData::Card(_), Some(enums::FutureUsage::OffSession), _) => ( + Some(TokenCreation { + token_type: TokenCreationType::Worldpay, + }), + Some(CustomerAgreement { + agreement_type: CustomerAgreementType::Subscription, + stored_card_usage: StoredCardUsageType::First, + }), + ), + // MIT + (PaymentMethodData::Card(_), _, Some(true)) => ( + None, + Some(CustomerAgreement { + agreement_type: CustomerAgreementType::Subscription, + stored_card_usage: StoredCardUsageType::Subsequent, + }), + ), + _ => (None, None), + } +} + +// Implementation for WorldpayPaymentsRequest using abstracted request +impl TryFrom<(&WorldpayRouterData<&T>, &Secret)> + for WorldpayPaymentsRequest +{ + type Error = error_stack::Report; + + fn try_from(req: (&WorldpayRouterData<&T>, &Secret)) -> Result { + let (item, entity_id) = req; + let worldpay_connector_metadata_object: WorldpayConnectorMetadataObject = + WorldpayConnectorMetadataObject::try_from(item.router_data.get_connector_meta_data())?; + + let merchant_name = worldpay_connector_metadata_object.merchant_name.ok_or( + errors::ConnectorError::InvalidConnectorConfig { + config: "metadata.merchant_name", + }, + )?; + + let is_mandate_payment = item.router_data.get_is_mandate_payment(); + let three_ds = create_three_ds_request(item.router_data, is_mandate_payment)?; + + let (token_creation, customer_agreement) = get_token_and_agreement( + item.router_data.get_payment_method_data(), + item.router_data.get_setup_future_usage(), + item.router_data.get_off_session(), + ); + + Ok(Self { + instruction: Instruction { + settlement: item.router_data.get_settlement_info(item.amount), + method: PaymentMethod::try_from(( + item.router_data.get_payment_method(), + item.router_data.get_payment_method_type(), + ))?, + payment_instrument: fetch_payment_instrument( + item.router_data.get_payment_method_data().clone(), + item.router_data.get_optional_billing_address(), + item.router_data.get_mandate_id(), + )?, + narrative: InstructionNarrative { + line1: merchant_name.expose(), + }, + value: PaymentValue { + amount: item.amount, + currency: item.router_data.get_currency(), + }, + debt_repayment: None, + three_ds, + token_creation, + customer_agreement, + }, + merchant: Merchant { + entity: entity_id.clone(), + ..Default::default() + }, + transaction_reference: item.router_data.get_connector_request_reference_id(), + customer: None, + }) + } +} + +pub struct WorldpayAuthType { + pub(super) api_key: Secret, + pub(super) entity_id: Secret, +} + +impl TryFrom<&ConnectorAuthType> for WorldpayAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &ConnectorAuthType) -> Result { + match auth_type { + // TODO: Remove this later, kept purely for backwards compatibility + ConnectorAuthType::BodyKey { api_key, key1 } => { + let auth_key = format!("{}:{}", key1.peek(), api_key.peek()); + let auth_header = format!("Basic {}", BASE64_ENGINE.encode(auth_key)); + Ok(Self { + api_key: Secret::new(auth_header), + entity_id: Secret::new("default".to_string()), + }) + } + ConnectorAuthType::SignatureKey { + api_key, + key1, + api_secret, + } => { + let auth_key = format!("{}:{}", key1.peek(), api_key.peek()); + let auth_header = format!("Basic {}", BASE64_ENGINE.encode(auth_key)); + Ok(Self { + api_key: Secret::new(auth_header), + entity_id: api_secret.clone(), + }) + } + _ => Err(errors::ConnectorError::FailedToObtainAuthType)?, + } + } +} + +impl From for enums::AttemptStatus { + fn from(item: PaymentOutcome) -> Self { + match item { + PaymentOutcome::Authorized => Self::Authorized, + PaymentOutcome::SentForSettlement => Self::Charged, + PaymentOutcome::ThreeDsDeviceDataRequired => Self::DeviceDataCollectionPending, + PaymentOutcome::ThreeDsAuthenticationFailed => Self::AuthenticationFailed, + PaymentOutcome::ThreeDsChallenged => Self::AuthenticationPending, + PaymentOutcome::SentForCancellation => Self::VoidInitiated, + PaymentOutcome::SentForPartialRefund | PaymentOutcome::SentForRefund => { + Self::AutoRefunded + } + PaymentOutcome::Refused | PaymentOutcome::FraudHighRisk => Self::Failure, + PaymentOutcome::ThreeDsUnavailable => Self::AuthenticationFailed, + } + } +} + +impl From for enums::RefundStatus { + fn from(item: PaymentOutcome) -> Self { + match item { + PaymentOutcome::SentForPartialRefund | PaymentOutcome::SentForRefund => Self::Success, + PaymentOutcome::Refused + | PaymentOutcome::FraudHighRisk + | PaymentOutcome::Authorized + | PaymentOutcome::SentForSettlement + | PaymentOutcome::ThreeDsDeviceDataRequired + | PaymentOutcome::ThreeDsAuthenticationFailed + | PaymentOutcome::ThreeDsChallenged + | PaymentOutcome::SentForCancellation + | PaymentOutcome::ThreeDsUnavailable => Self::Failure, + } + } +} + +impl From<&EventType> for enums::AttemptStatus { + fn from(value: &EventType) -> Self { + match value { + EventType::SentForAuthorization => Self::Authorizing, + EventType::SentForSettlement => Self::Charged, + EventType::Settled => Self::Charged, + EventType::Authorized => Self::Authorized, + EventType::Refused + | EventType::SettlementFailed + | EventType::Expired + | EventType::Cancelled + | EventType::Error => Self::Failure, + EventType::SentForRefund + | EventType::RefundFailed + | EventType::Refunded + | EventType::Unknown => Self::Pending, + } + } +} + +impl From for enums::RefundStatus { + fn from(value: EventType) -> Self { + match value { + EventType::Refunded | EventType::SentForRefund => Self::Success, + EventType::RefundFailed => Self::Failure, + EventType::Authorized + | EventType::Cancelled + | EventType::Settled + | EventType::Refused + | EventType::Error + | EventType::SentForSettlement + | EventType::SentForAuthorization + | EventType::SettlementFailed + | EventType::Expired + | EventType::Unknown => Self::Pending, + } + } +} + +impl + ForeignTryFrom<( + ResponseRouterData, + Option, + )> for RouterData +{ + type Error = error_stack::Report; + fn foreign_try_from( + item: ( + ResponseRouterData, + Option, + ), + ) -> Result { + let (router_data, optional_correlation_id) = item; + let (description, redirection_data, mandate_reference, error) = router_data + .response + .other_fields + .as_ref() + .map(|other_fields| match other_fields { + WorldpayPaymentResponseFields::AuthorizedResponse(res) => ( + res.description.clone(), + None, + res.token.as_ref().map(|mandate_token| MandateReference { + connector_mandate_id: Some(mandate_token.href.clone().expose()), + payment_method_id: Some(mandate_token.token_id.clone()), + mandate_metadata: None, + connector_mandate_request_reference_id: None, + }), + None, + ), + WorldpayPaymentResponseFields::DDCResponse(res) => ( + None, + Some(RedirectForm::WorldpayDDCForm { + endpoint: res.device_data_collection.url.clone(), + method: common_utils::request::Method::Post, + collection_id: Some("SessionId".to_string()), + form_fields: HashMap::from([ + ( + "Bin".to_string(), + res.device_data_collection.bin.clone().expose(), + ), + ( + "JWT".to_string(), + res.device_data_collection.jwt.clone().expose(), + ), + ]), + }), + None, + None, + ), + WorldpayPaymentResponseFields::ThreeDsChallenged(res) => ( + None, + Some(RedirectForm::Form { + endpoint: res.challenge.url.to_string(), + method: common_utils::request::Method::Post, + form_fields: HashMap::from([( + "JWT".to_string(), + res.challenge.jwt.clone().expose(), + )]), + }), + None, + None, + ), + WorldpayPaymentResponseFields::RefusedResponse(res) => ( + None, + None, + None, + Some((res.refusal_code.clone(), res.refusal_description.clone())), + ), + WorldpayPaymentResponseFields::FraudHighRisk(_) => (None, None, None, None), + }) + .unwrap_or((None, None, None, None)); + let worldpay_status = router_data.response.outcome.clone(); + let optional_error_message = match worldpay_status { + PaymentOutcome::ThreeDsAuthenticationFailed => { + Some("3DS authentication failed from issuer".to_string()) + } + PaymentOutcome::ThreeDsUnavailable => { + Some("3DS authentication unavailable from issuer".to_string()) + } + PaymentOutcome::FraudHighRisk => Some("Transaction marked as high risk".to_string()), + _ => None, + }; + let status = enums::AttemptStatus::from(worldpay_status.clone()); + let response = match (optional_error_message, error) { + (None, None) => Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::foreign_try_from(( + router_data.response, + optional_correlation_id.clone(), + ))?, + redirection_data: Box::new(redirection_data), + mandate_reference: Box::new(mandate_reference), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: optional_correlation_id.clone(), + incremental_authorization_allowed: None, + charge_id: None, + }), + (Some(reason), _) => Err(ErrorResponse { + code: worldpay_status.to_string(), + message: reason.clone(), + reason: Some(reason), + status_code: router_data.http_code, + attempt_status: Some(status), + connector_transaction_id: optional_correlation_id, + }), + (_, Some((code, message))) => Err(ErrorResponse { + code, + message: message.clone(), + reason: Some(message), + status_code: router_data.http_code, + attempt_status: Some(status), + connector_transaction_id: optional_correlation_id, + }), + }; + Ok(Self { + status, + description, + response, + ..router_data.data + }) + } +} + +impl TryFrom<(&types::PaymentsCaptureRouterData, MinorUnit)> for WorldpayPartialRequest { + type Error = error_stack::Report; + fn try_from(req: (&types::PaymentsCaptureRouterData, MinorUnit)) -> Result { + let (item, amount) = req; + Ok(Self { + reference: item.payment_id.clone().replace("_", "-"), + value: PaymentValue { + amount: amount.get_amount_as_i64(), + currency: item.request.currency, + }, + }) + } +} + +impl TryFrom<(&types::RefundsRouterData, MinorUnit)> for WorldpayPartialRequest { + type Error = error_stack::Report; + fn try_from(req: (&types::RefundsRouterData, MinorUnit)) -> Result { + let (item, amount) = req; + Ok(Self { + reference: item.request.refund_id.clone().replace("_", "-"), + value: PaymentValue { + amount: amount.get_amount_as_i64(), + currency: item.request.currency, + }, + }) + } +} + +impl TryFrom for WorldpayEventResponse { + type Error = error_stack::Report; + fn try_from(event: WorldpayWebhookEventType) -> Result { + Ok(Self { + last_event: event.event_details.event_type, + links: None, + }) + } +} + +impl ForeignTryFrom<(WorldpayPaymentsResponse, Option)> for ResponseIdStr { + type Error = error_stack::Report; + fn foreign_try_from( + item: (WorldpayPaymentsResponse, Option), + ) -> Result { + get_resource_id(item.0, item.1, |id| Self { id }) + } +} + +impl ForeignTryFrom<(WorldpayPaymentsResponse, Option)> for ResponseId { + type Error = error_stack::Report; + fn foreign_try_from( + item: (WorldpayPaymentsResponse, Option), + ) -> Result { + get_resource_id(item.0, item.1, Self::ConnectorTransactionId) + } +} + +impl TryFrom<&types::PaymentsCompleteAuthorizeRouterData> for WorldpayCompleteAuthorizationRequest { + type Error = error_stack::Report; + fn try_from(item: &types::PaymentsCompleteAuthorizeRouterData) -> Result { + let params = item + .request + .redirect_response + .as_ref() + .and_then(|redirect_response| redirect_response.params.as_ref()) + .ok_or(errors::ConnectorError::ResponseDeserializationFailed)?; + serde_urlencoded::from_str::(params.peek()) + .change_context(errors::ConnectorError::ResponseDeserializationFailed) + } +} diff --git a/crates/hyperswitch_connectors/src/connectors/xendit.rs b/crates/hyperswitch_connectors/src/connectors/xendit.rs new file mode 100644 index 000000000000..571bd87e83c3 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/xendit.rs @@ -0,0 +1,568 @@ +pub mod transformers; + +use common_utils::{ + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, +}; +use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::{ + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, + RefundSyncRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks, +}; +use masking::{ExposeInterface, Mask}; +use transformers as xendit; + +use crate::{constants::headers, types::ResponseRouterData, utils}; + +#[derive(Clone)] +pub struct Xendit { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl Xendit { + pub fn new() -> &'static Self { + &Self { + amount_converter: &StringMinorUnitForConnector, + } + } +} + +impl api::Payment for Xendit {} +impl api::PaymentSession for Xendit {} +impl api::ConnectorAccessToken for Xendit {} +impl api::MandateSetup for Xendit {} +impl api::PaymentAuthorize for Xendit {} +impl api::PaymentSync for Xendit {} +impl api::PaymentCapture for Xendit {} +impl api::PaymentVoid for Xendit {} +impl api::Refund for Xendit {} +impl api::RefundExecute for Xendit {} +impl api::RefundSync for Xendit {} +impl api::PaymentToken for Xendit {} + +impl ConnectorIntegration + for Xendit +{ + // Not Implemented (R) +} + +impl ConnectorCommonExt for Xendit +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } +} + +impl ConnectorCommon for Xendit { + fn id(&self) -> &'static str { + "xendit" + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Base + // TODO! Check connector documentation, on which unit they are processing the currency. + // If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor, + // if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { + connectors.xendit.base_url.as_ref() + } + + fn get_auth_header( + &self, + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + let auth = xendit::XenditAuthType::try_from(auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + Ok(vec![( + headers::AUTHORIZATION.to_string(), + auth.api_key.expose().into_masked(), + )]) + } + + fn build_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: xendit::XenditErrorResponse = res + .response + .parse_struct("XenditErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.code, + message: response.message, + reason: response.reason, + attempt_status: None, + connector_transaction_id: None, + }) + } +} + +impl ConnectorValidation for Xendit { + //TODO: implement functions when support enabled +} + +impl ConnectorIntegration for Xendit { + //TODO: implement sessions flow +} + +impl ConnectorIntegration for Xendit {} + +impl ConnectorIntegration for Xendit {} + +impl ConnectorIntegration for Xendit { + fn get_headers( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + + let connector_router_data = xendit::XenditRouterData::from((amount, req)); + let connector_req = xendit::XenditPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: xendit::XenditPaymentsResponse = res + .response + .parse_struct("Xendit PaymentsAuthorizeResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Xendit { + fn get_headers( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: xendit::XenditPaymentsResponse = res + .response + .parse_struct("xendit PaymentsSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Xendit { + fn get_headers( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsCaptureType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCaptureType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCaptureRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: xendit::XenditPaymentsResponse = res + .response + .parse_struct("Xendit PaymentsCaptureResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Xendit {} + +impl ConnectorIntegration for Xendit { + fn get_headers( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let refund_amount = utils::convert_amount( + self.amount_converter, + req.request.minor_refund_amount, + req.request.currency, + )?; + + let connector_router_data = xendit::XenditRouterData::from((refund_amount, req)); + let connector_req = xendit::XenditRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundExecuteType::get_headers( + self, req, connectors, + )?) + .set_body(types::RefundExecuteType::get_request_body( + self, req, connectors, + )?) + .build(); + Ok(Some(request)) + } + + fn handle_response( + &self, + data: &RefundsRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult, errors::ConnectorError> { + let response: xendit::RefundResponse = + res.response + .parse_struct("xendit RefundResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Xendit { + fn get_headers( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &RefundSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::RefundSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundSyncType::get_headers(self, req, connectors)?) + .set_body(types::RefundSyncType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &RefundSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: xendit::RefundResponse = res + .response + .parse_struct("xendit RefundSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +#[async_trait::async_trait] +impl webhooks::IncomingWebhook for Xendit { + fn get_webhook_object_reference_id( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_event_type( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_resource_object( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } +} + +impl ConnectorSpecifications for Xendit {} diff --git a/crates/hyperswitch_connectors/src/connectors/xendit/transformers.rs b/crates/hyperswitch_connectors/src/connectors/xendit/transformers.rs new file mode 100644 index 000000000000..c9d4cd2583c2 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/xendit/transformers.rs @@ -0,0 +1,228 @@ +use common_enums::enums; +use common_utils::types::StringMinorUnit; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{ConnectorAuthType, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{PaymentsAuthorizeRouterData, RefundsRouterData}, +}; +use hyperswitch_interfaces::errors; +use masking::Secret; +use serde::{Deserialize, Serialize}; + +use crate::{ + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::PaymentsAuthorizeRequestData, +}; + +//TODO: Fill the struct with respective fields +pub struct XenditRouterData { + pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub router_data: T, +} + +impl From<(StringMinorUnit, T)> for XenditRouterData { + fn from((amount, item): (StringMinorUnit, T)) -> Self { + //Todo : use utils to convert the amount to the type of amount that a connector accepts + Self { + amount, + router_data: item, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, PartialEq)] +pub struct XenditPaymentsRequest { + amount: StringMinorUnit, + card: XenditCard, +} + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct XenditCard { + number: cards::CardNumber, + expiry_month: Secret, + expiry_year: Secret, + cvc: Secret, + complete: bool, +} + +impl TryFrom<&XenditRouterData<&PaymentsAuthorizeRouterData>> for XenditPaymentsRequest { + type Error = error_stack::Report; + fn try_from( + item: &XenditRouterData<&PaymentsAuthorizeRouterData>, + ) -> Result { + match item.router_data.request.payment_method_data.clone() { + PaymentMethodData::Card(req_card) => { + let card = XenditCard { + number: req_card.card_number, + expiry_month: req_card.card_exp_month, + expiry_year: req_card.card_exp_year, + cvc: req_card.card_cvc, + complete: item.router_data.request.is_auto_capture()?, + }; + Ok(Self { + amount: item.amount.clone(), + card, + }) + } + _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), + } + } +} + +//TODO: Fill the struct with respective fields +// Auth Struct +pub struct XenditAuthType { + pub(super) api_key: Secret, +} + +impl TryFrom<&ConnectorAuthType> for XenditAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &ConnectorAuthType) -> Result { + match auth_type { + ConnectorAuthType::HeaderKey { api_key } => Ok(Self { + api_key: api_key.to_owned(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} +// PaymentsResponse +//TODO: Append the remaining status flags +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum XenditPaymentStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for common_enums::AttemptStatus { + fn from(item: XenditPaymentStatus) -> Self { + match item { + XenditPaymentStatus::Succeeded => Self::Charged, + XenditPaymentStatus::Failed => Self::Failure, + XenditPaymentStatus::Processing => Self::Authorizing, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct XenditPaymentsResponse { + status: XenditPaymentStatus, + id: String, +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + Ok(Self { + status: common_enums::AttemptStatus::from(item.response.status), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +// REFUND : +// Type definition for RefundRequest +#[derive(Default, Debug, Serialize)] +pub struct XenditRefundRequest { + pub amount: StringMinorUnit, +} + +impl TryFrom<&XenditRouterData<&RefundsRouterData>> for XenditRefundRequest { + type Error = error_stack::Report; + fn try_from(item: &XenditRouterData<&RefundsRouterData>) -> Result { + Ok(Self { + amount: item.amount.to_owned(), + }) + } +} + +// Type definition for Refund Response + +#[allow(dead_code)] +#[derive(Debug, Serialize, Default, Deserialize, Clone)] +pub enum RefundStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for enums::RefundStatus { + fn from(item: RefundStatus) -> Self { + match item { + RefundStatus::Succeeded => Self::Success, + RefundStatus::Failed => Self::Failure, + RefundStatus::Processing => Self::Pending, + //TODO: Review mapping + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct RefundResponse { + id: String, + status: RefundStatus, +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct XenditErrorResponse { + pub status_code: u16, + pub code: String, + pub message: String, + pub reason: Option, +} diff --git a/crates/router/src/connector/zen.rs b/crates/hyperswitch_connectors/src/connectors/zen.rs similarity index 64% rename from crates/router/src/connector/zen.rs rename to crates/hyperswitch_connectors/src/connectors/zen.rs index 2d522dbdf1bd..70703b316432 100644 --- a/crates/router/src/connector/zen.rs +++ b/crates/hyperswitch_connectors/src/connectors/zen.rs @@ -2,34 +2,52 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; +use api_models::webhooks::IncomingWebhookEvent; +use common_enums::{CallConnectorAction, PaymentAction}; +use common_utils::{ + crypto, + errors::CustomResult, + ext_traits::{ByteSliceExt, BytesExt}, + request::{Method, Request, RequestBuilder, RequestContent}, +}; use error_stack::ResultExt; -use masking::{PeekInterface, Secret}; -use transformers as zen; -use uuid::Uuid; - -use self::transformers::{ZenPaymentStatus, ZenWebhookTxnType}; -use crate::{ - configs::settings, - consts, - core::{ - errors::{self, CustomResult}, - payments, +use hyperswitch_domain_models::{ + api::ApplicationResponse, + payment_method_data::PaymentMethodData, + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, }, - events::connector_api_logs::ConnectorEvent, - headers, - services::{ - self, - request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, types::{ - self, - api::{self, ConnectorCommon, ConnectorCommonExt}, - domain, ErrorResponse, Response, + PaymentsAuthorizeRouterData, PaymentsSyncRouterData, RefundSyncRouterData, + RefundsRouterData, }, - utils::BytesExt, }; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorRedirectResponse, + ConnectorSpecifications, ConnectorValidation, + }, + configs::Connectors, + consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE}, + errors, + events::connector_api_logs::ConnectorEvent, + types::{PaymentsAuthorizeType, PaymentsSyncType, RefundExecuteType, RefundSyncType, Response}, + webhooks::{IncomingWebhook, IncomingWebhookFlowError, IncomingWebhookRequestDetails}, +}; +use masking::{Mask, PeekInterface, Secret}; +use transformers::{self as zen, ZenPaymentStatus, ZenWebhookTxnType}; +use uuid::Uuid; + +use crate::{constants::headers, types::ResponseRouterData}; #[derive(Debug, Clone)] pub struct Zen; @@ -48,7 +66,7 @@ impl api::RefundExecute for Zen {} impl api::RefundSync for Zen {} impl Zen { - fn get_default_header() -> (String, request::Maskable) { + fn get_default_header() -> (String, masking::Maskable) { ("request-id".to_string(), Uuid::new_v4().to_string().into()) } } @@ -59,9 +77,9 @@ where { fn build_headers( &self, - req: &types::RouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let mut headers = vec![( headers::CONTENT_TYPE.to_string(), self.get_content_type().to_string().into(), @@ -87,14 +105,14 @@ impl ConnectorCommon for Zen { mime::APPLICATION_JSON.essence_str() } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.zen.base_url.as_ref() } fn get_auth_header( &self, - auth_type: &types::ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { let auth = zen::ZenAuthType::try_from(auth_type)?; Ok(vec![( headers::AUTHORIZATION.to_string(), @@ -120,13 +138,9 @@ impl ConnectorCommon for Zen { code: response .error .clone() - .map_or(consts::NO_ERROR_CODE.to_string(), |error| error.code), + .map_or(NO_ERROR_CODE.to_string(), |error| error.code), message: response.error.map_or_else( - || { - response - .message - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()) - }, + || response.message.unwrap_or(NO_ERROR_MESSAGE.to_string()), |error| error.message, ), reason: None, @@ -139,7 +153,7 @@ impl ConnectorCommon for Zen { impl ConnectorValidation for Zen { fn validate_psync_reference_id( &self, - _data: &hyperswitch_domain_models::router_request_types::PaymentsSyncData, + _data: &PaymentsSyncData, _is_three_ds: bool, _status: common_enums::enums::AttemptStatus, _connector_meta_data: Option, @@ -149,58 +163,37 @@ impl ConnectorValidation for Zen { } } -impl ConnectorIntegration - for Zen -{ +impl ConnectorIntegration for Zen { //TODO: implement sessions flow } -impl - ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Zen +impl ConnectorIntegration + for Zen { // Not Implemented (R) } -impl ConnectorIntegration - for Zen -{ -} +impl ConnectorIntegration for Zen {} -impl - ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Zen -{ +impl ConnectorIntegration for Zen { fn build_request( &self, - _req: &types::RouterData< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - >, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err(errors::ConnectorError::NotImplemented("Setup Mandate flow for Zen".to_string()).into()) } } -impl ConnectorIntegration - for Zen -{ +impl ConnectorIntegration for Zen { fn get_headers( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let mut headers = self.build_headers(req, connectors)?; let api_headers = match req.request.payment_method_data { - domain::payments::PaymentMethodData::Wallet(_) => None, + PaymentMethodData::Wallet(_) => None, _ => Some(Self::get_default_header()), }; if let Some(api_header) = api_headers { @@ -215,11 +208,11 @@ impl ConnectorIntegration CustomResult { let endpoint = match &req.request.payment_method_data { - domain::payments::PaymentMethodData::Wallet(_) => { + PaymentMethodData::Wallet(_) => { let base_url = connectors .zen .secondary_base_url @@ -234,8 +227,8 @@ impl ConnectorIntegration CustomResult { let connector_router_data = zen::ZenRouterData::try_from(( &self.get_currency_unit(), @@ -249,20 +242,16 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsAuthorizeType::get_url( - self, req, connectors, - )?) + RequestBuilder::new() + .method(Method::Post) + .url(&PaymentsAuthorizeType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::PaymentsAuthorizeType::get_headers( - self, req, connectors, - )?) - .set_body(types::PaymentsAuthorizeType::get_request_body( + .headers(PaymentsAuthorizeType::get_headers(self, req, connectors)?) + .set_body(PaymentsAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -271,17 +260,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: zen::ZenPaymentsResponse = res .response .parse_struct("Zen PaymentsAuthorizeResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -297,14 +286,12 @@ impl ConnectorIntegration - for Zen -{ +impl ConnectorIntegration for Zen { fn get_headers( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let mut headers = self.build_headers(req, connectors)?; headers.push(Self::get_default_header()); Ok(headers) @@ -316,8 +303,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}v1/transactions/merchant/{}", @@ -328,32 +315,32 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) - .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Get) + .url(&PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .headers(PaymentsSyncType::get_headers(self, req, connectors)?) .build(), )) } fn handle_response( &self, - data: &types::PaymentsSyncRouterData, + data: &PaymentsSyncRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: zen::ZenPaymentsResponse = res .response .parse_struct("zen PaymentsSyncResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -369,18 +356,12 @@ impl ConnectorIntegration - for Zen -{ +impl ConnectorIntegration for Zen { fn build_request( &self, - _req: &types::RouterData< - api::Capture, - types::PaymentsCaptureData, - types::PaymentsResponseData, - >, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err(errors::ConnectorError::FlowNotSupported { flow: "Capture".to_owned(), connector: "Zen".to_owned(), @@ -389,14 +370,12 @@ impl ConnectorIntegration - for Zen -{ +impl ConnectorIntegration for Zen { fn build_request( &self, - _req: &types::RouterData, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err(errors::ConnectorError::FlowNotSupported { flow: "Void".to_owned(), connector: "Zen".to_owned(), @@ -405,12 +384,12 @@ impl ConnectorIntegration for Zen { +impl ConnectorIntegration for Zen { fn get_headers( &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let mut headers = self.build_headers(req, connectors)?; headers.push(Self::get_default_header()); Ok(headers) @@ -422,8 +401,8 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, + _req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!( "{}v1/transactions/refund", @@ -433,8 +412,8 @@ impl ConnectorIntegration, - _connectors: &settings::Connectors, + req: &RefundsRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_router_data = zen::ZenRouterData::try_from(( &self.get_currency_unit(), @@ -448,29 +427,25 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::RefundExecuteType::get_headers( - self, req, connectors, - )?) - .set_body(types::RefundExecuteType::get_request_body( - self, req, connectors, - )?) + .headers(RefundExecuteType::get_headers(self, req, connectors)?) + .set_body(RefundExecuteType::get_request_body(self, req, connectors)?) .build(); Ok(Some(request)) } fn handle_response( &self, - data: &types::RefundsRouterData, + data: &RefundsRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult, errors::ConnectorError> { let response: zen::RefundResponse = res .response .parse_struct("zen RefundResponse") @@ -478,7 +453,7 @@ impl ConnectorIntegration for Zen { +impl ConnectorIntegration for Zen { fn get_headers( &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let mut headers = self.build_headers(req, connectors)?; headers.push(Self::get_default_header()); Ok(headers) @@ -511,8 +486,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}v1/transactions/merchant/{}", @@ -523,25 +498,25 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) - .url(&types::RefundSyncType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Get) + .url(&RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::RefundSyncType::get_headers(self, req, connectors)?) + .headers(RefundSyncType::get_headers(self, req, connectors)?) .build(), )) } fn handle_response( &self, - data: &types::RefundSyncRouterData, + data: &RefundSyncRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: zen::RefundResponse = res .response .parse_struct("zen RefundSyncResponse") @@ -550,7 +525,7 @@ impl ConnectorIntegration, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Ok(Box::new(crypto::Sha256)) } fn get_webhook_source_verification_signature( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { let webhook_body: zen::ZenWebhookSignature = request @@ -590,7 +565,7 @@ impl api::IncomingWebhook for Zen { fn get_webhook_source_verification_message( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, _merchant_id: &common_utils::id_type::MerchantId, _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { @@ -610,7 +585,7 @@ impl api::IncomingWebhook for Zen { async fn verify_webhook_source( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, merchant_id: &common_utils::id_type::MerchantId, connector_webhook_details: Option, _connector_account_details: crypto::Encryptable>, @@ -641,7 +616,7 @@ impl api::IncomingWebhook for Zen { fn get_webhook_object_reference_id( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult { let webhook_body: zen::ZenWebhookObjectReference = request .body @@ -663,8 +638,8 @@ impl api::IncomingWebhook for Zen { fn get_webhook_event_type( &self, - request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { let details: zen::ZenWebhookEventType = request .body .parse_struct("ZenWebhookEventType") @@ -672,22 +647,22 @@ impl api::IncomingWebhook for Zen { Ok(match &details.transaction_type { ZenWebhookTxnType::TrtPurchase => match &details.status { - ZenPaymentStatus::Rejected => api::IncomingWebhookEvent::PaymentIntentFailure, - ZenPaymentStatus::Accepted => api::IncomingWebhookEvent::PaymentIntentSuccess, + ZenPaymentStatus::Rejected => IncomingWebhookEvent::PaymentIntentFailure, + ZenPaymentStatus::Accepted => IncomingWebhookEvent::PaymentIntentSuccess, _ => Err(errors::ConnectorError::WebhookEventTypeNotFound)?, }, ZenWebhookTxnType::TrtRefund => match &details.status { - ZenPaymentStatus::Rejected => api::IncomingWebhookEvent::RefundFailure, - ZenPaymentStatus::Accepted => api::IncomingWebhookEvent::RefundSuccess, + ZenPaymentStatus::Rejected => IncomingWebhookEvent::RefundFailure, + ZenPaymentStatus::Accepted => IncomingWebhookEvent::RefundSuccess, _ => Err(errors::ConnectorError::WebhookEventTypeNotFound)?, }, - ZenWebhookTxnType::Unknown => api::IncomingWebhookEvent::EventNotSupported, + ZenWebhookTxnType::Unknown => IncomingWebhookEvent::EventNotSupported, }) } fn get_webhook_resource_object( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { let reference_object: serde_json::Value = serde_json::from_slice(request.body) .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?; @@ -695,30 +670,30 @@ impl api::IncomingWebhook for Zen { } fn get_webhook_api_response( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult, errors::ConnectorError> - { - Ok(services::api::ApplicationResponse::Json( - serde_json::json!({ - "status": "ok" - }), - )) + _request: &IncomingWebhookRequestDetails<'_>, + _error_kind: Option, + ) -> CustomResult, errors::ConnectorError> { + Ok(ApplicationResponse::Json(serde_json::json!({ + "status": "ok" + }))) } } -impl services::ConnectorRedirectResponse for Zen { +impl ConnectorRedirectResponse for Zen { fn get_flow_type( &self, _query_params: &str, _json_payload: Option, - action: services::PaymentAction, - ) -> CustomResult { + action: PaymentAction, + ) -> CustomResult { match action { - services::PaymentAction::PSync - | services::PaymentAction::CompleteAuthorize - | services::PaymentAction::PaymentAuthenticateCompleteAuthorize => { - Ok(payments::CallConnectorAction::Trigger) + PaymentAction::PSync + | PaymentAction::CompleteAuthorize + | PaymentAction::PaymentAuthenticateCompleteAuthorize => { + Ok(CallConnectorAction::Trigger) } } } } + +impl ConnectorSpecifications for Zen {} diff --git a/crates/router/src/connector/zen/transformers.rs b/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs similarity index 71% rename from crates/router/src/connector/zen/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/zen/transformers.rs index d2e8ce40283b..cc6795f157a6 100644 --- a/crates/router/src/connector/zen/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs @@ -1,20 +1,39 @@ use cards::CardNumber; -use common_utils::{ext_traits::ValueExt, pii}; +use common_enums::enums; +use common_utils::{ + errors::CustomResult, + ext_traits::{OptionExt, ValueExt}, + pii::{self}, + request::Method, +}; use error_stack::ResultExt; +use hyperswitch_domain_models::{ + payment_method_data::{ + BankDebitData, BankRedirectData, BankTransferData, Card, CardRedirectData, GiftCardData, + PayLaterData, PaymentMethodData, VoucherData, WalletData, + }, + router_data::{ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::{BrowserInformation, ResponseId}, + router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData}, + types, +}; +use hyperswitch_interfaces::{ + api, + consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE}, + errors, +}; use masking::{ExposeInterface, PeekInterface, Secret}; use ring::digest; use serde::{Deserialize, Serialize}; use strum::Display; use crate::{ - connector::utils::{ - self, BrowserInformationData, CardData, PaymentsAuthorizeRequestData, RouterData, + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::{ + self, BrowserInformationData, CardData, ForeignTryFrom, PaymentsAuthorizeRequestData, + RouterData as _, }, - consts, - core::errors::{self, CustomResult}, - services::{self, Method}, - types::{self, api, domain, storage::enums, transformers::ForeignTryFrom}, - utils::OptionExt, }; #[derive(Debug, Serialize)] @@ -41,10 +60,10 @@ pub struct ZenAuthType { pub(super) api_key: Secret, } -impl TryFrom<&types::ConnectorAuthType> for ZenAuthType { +impl TryFrom<&ConnectorAuthType> for ZenAuthType { type Error = error_stack::Report; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { - if let types::ConnectorAuthType::HeaderKey { api_key } = auth_type { + fn try_from(auth_type: &ConnectorAuthType) -> Result { + if let ConnectorAuthType::HeaderKey { api_key } = auth_type { Ok(Self { api_key: api_key.to_owned(), }) @@ -191,18 +210,10 @@ pub struct WalletSessionData { pub pay_wall_secret: Option>, } -impl - TryFrom<( - &ZenRouterData<&types::PaymentsAuthorizeRouterData>, - &domain::Card, - )> for ZenPaymentsRequest -{ +impl TryFrom<(&ZenRouterData<&types::PaymentsAuthorizeRouterData>, &Card)> for ZenPaymentsRequest { type Error = error_stack::Report; fn try_from( - value: ( - &ZenRouterData<&types::PaymentsAuthorizeRouterData>, - &domain::Card, - ), + value: (&ZenRouterData<&types::PaymentsAuthorizeRouterData>, &Card), ) -> Result { let (item, ccard) = value; let browser_info = item.router_data.request.get_browser_info()?; @@ -245,14 +256,14 @@ impl impl TryFrom<( &ZenRouterData<&types::PaymentsAuthorizeRouterData>, - &domain::VoucherData, + &VoucherData, )> for ZenPaymentsRequest { type Error = error_stack::Report; fn try_from( value: ( &ZenRouterData<&types::PaymentsAuthorizeRouterData>, - &domain::VoucherData, + &VoucherData, ), ) -> Result { let (item, voucher_data) = value; @@ -266,20 +277,20 @@ impl return_url: item.router_data.request.get_router_return_url()?, }); let payment_channel = match voucher_data { - domain::VoucherData::Boleto { .. } => ZenPaymentChannels::PclBoacompraBoleto, - domain::VoucherData::Efecty => ZenPaymentChannels::PclBoacompraEfecty, - domain::VoucherData::PagoEfectivo => ZenPaymentChannels::PclBoacompraPagoefectivo, - domain::VoucherData::RedCompra => ZenPaymentChannels::PclBoacompraRedcompra, - domain::VoucherData::RedPagos => ZenPaymentChannels::PclBoacompraRedpagos, - domain::VoucherData::Oxxo { .. } - | domain::VoucherData::Alfamart { .. } - | domain::VoucherData::Indomaret { .. } - | domain::VoucherData::SevenEleven { .. } - | domain::VoucherData::Lawson { .. } - | domain::VoucherData::MiniStop { .. } - | domain::VoucherData::FamilyMart { .. } - | domain::VoucherData::Seicomart { .. } - | domain::VoucherData::PayEasy { .. } => Err(errors::ConnectorError::NotImplemented( + VoucherData::Boleto { .. } => ZenPaymentChannels::PclBoacompraBoleto, + VoucherData::Efecty => ZenPaymentChannels::PclBoacompraEfecty, + VoucherData::PagoEfectivo => ZenPaymentChannels::PclBoacompraPagoefectivo, + VoucherData::RedCompra => ZenPaymentChannels::PclBoacompraRedcompra, + VoucherData::RedPagos => ZenPaymentChannels::PclBoacompraRedpagos, + VoucherData::Oxxo { .. } + | VoucherData::Alfamart { .. } + | VoucherData::Indomaret { .. } + | VoucherData::SevenEleven { .. } + | VoucherData::Lawson { .. } + | VoucherData::MiniStop { .. } + | VoucherData::FamilyMart { .. } + | VoucherData::Seicomart { .. } + | VoucherData::PayEasy { .. } => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Zen"), ))?, }; @@ -299,14 +310,14 @@ impl impl TryFrom<( &ZenRouterData<&types::PaymentsAuthorizeRouterData>, - &Box, + &Box, )> for ZenPaymentsRequest { type Error = error_stack::Report; fn try_from( value: ( &ZenRouterData<&types::PaymentsAuthorizeRouterData>, - &Box, + &Box, ), ) -> Result { let (item, bank_transfer_data) = value; @@ -320,22 +331,22 @@ impl return_url: item.router_data.request.get_router_return_url()?, }); let payment_channel = match **bank_transfer_data { - domain::BankTransferData::MultibancoBankTransfer { .. } => { + BankTransferData::MultibancoBankTransfer { .. } => { ZenPaymentChannels::PclBoacompraMultibanco } - domain::BankTransferData::Pix { .. } => ZenPaymentChannels::PclBoacompraPix, - domain::BankTransferData::Pse { .. } => ZenPaymentChannels::PclBoacompraPse, - domain::BankTransferData::SepaBankTransfer { .. } - | domain::BankTransferData::AchBankTransfer { .. } - | domain::BankTransferData::BacsBankTransfer { .. } - | domain::BankTransferData::PermataBankTransfer { .. } - | domain::BankTransferData::BcaBankTransfer { .. } - | domain::BankTransferData::BniVaBankTransfer { .. } - | domain::BankTransferData::BriVaBankTransfer { .. } - | domain::BankTransferData::CimbVaBankTransfer { .. } - | domain::BankTransferData::DanamonVaBankTransfer { .. } - | domain::BankTransferData::LocalBankTransfer { .. } - | domain::BankTransferData::MandiriVaBankTransfer { .. } => { + BankTransferData::Pix { .. } => ZenPaymentChannels::PclBoacompraPix, + BankTransferData::Pse { .. } => ZenPaymentChannels::PclBoacompraPse, + BankTransferData::SepaBankTransfer { .. } + | BankTransferData::AchBankTransfer { .. } + | BankTransferData::BacsBankTransfer { .. } + | BankTransferData::PermataBankTransfer { .. } + | BankTransferData::BcaBankTransfer { .. } + | BankTransferData::BniVaBankTransfer { .. } + | BankTransferData::BriVaBankTransfer { .. } + | BankTransferData::CimbVaBankTransfer { .. } + | BankTransferData::DanamonVaBankTransfer { .. } + | BankTransferData::LocalBankTransfer { .. } + | BankTransferData::MandiriVaBankTransfer { .. } => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Zen"), ))? @@ -437,14 +448,14 @@ impl impl TryFrom<( &ZenRouterData<&types::PaymentsAuthorizeRouterData>, - &domain::WalletData, + &WalletData, )> for ZenPaymentsRequest { type Error = error_stack::Report; fn try_from( (item, wallet_data): ( &ZenRouterData<&types::PaymentsAuthorizeRouterData>, - &domain::WalletData, + &WalletData, ), ) -> Result { let amount = item.amount.to_owned(); @@ -453,7 +464,7 @@ impl .parse_value("SessionObject") .change_context(errors::ConnectorError::RequestEncodingFailed)?; let (specified_payment_channel, session_data) = match wallet_data { - domain::WalletData::ApplePayRedirect(_) => ( + WalletData::ApplePayRedirect(_) => ( ZenPaymentChannels::PclApplepay, session .apple_pay @@ -461,7 +472,7 @@ impl wallet_name: "Apple Pay".to_string(), })?, ), - domain::WalletData::GooglePayRedirect(_) => ( + WalletData::GooglePayRedirect(_) => ( ZenPaymentChannels::PclGooglepay, session .google_pay @@ -469,31 +480,32 @@ impl wallet_name: "Google Pay".to_string(), })?, ), - domain::WalletData::WeChatPayRedirect(_) - | domain::WalletData::PaypalRedirect(_) - | domain::WalletData::ApplePay(_) - | domain::WalletData::GooglePay(_) - | domain::WalletData::AliPayQr(_) - | domain::WalletData::AliPayRedirect(_) - | domain::WalletData::AliPayHkRedirect(_) - | domain::WalletData::MomoRedirect(_) - | domain::WalletData::KakaoPayRedirect(_) - | domain::WalletData::GoPayRedirect(_) - | domain::WalletData::GcashRedirect(_) - | domain::WalletData::ApplePayThirdPartySdk(_) - | domain::WalletData::DanaRedirect {} - | domain::WalletData::GooglePayThirdPartySdk(_) - | domain::WalletData::MbWayRedirect(_) - | domain::WalletData::MobilePayRedirect(_) - | domain::WalletData::PaypalSdk(_) - | domain::WalletData::SamsungPay(_) - | domain::WalletData::TwintRedirect {} - | domain::WalletData::VippsRedirect {} - | domain::WalletData::TouchNGoRedirect(_) - | domain::WalletData::CashappQr(_) - | domain::WalletData::SwishQr(_) - | domain::WalletData::WeChatPayQr(_) - | domain::WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( + WalletData::WeChatPayRedirect(_) + | WalletData::PaypalRedirect(_) + | WalletData::ApplePay(_) + | WalletData::GooglePay(_) + | WalletData::AliPayQr(_) + | WalletData::AliPayRedirect(_) + | WalletData::AliPayHkRedirect(_) + | WalletData::MomoRedirect(_) + | WalletData::KakaoPayRedirect(_) + | WalletData::GoPayRedirect(_) + | WalletData::GcashRedirect(_) + | WalletData::ApplePayThirdPartySdk(_) + | WalletData::DanaRedirect {} + | WalletData::GooglePayThirdPartySdk(_) + | WalletData::MbWayRedirect(_) + | WalletData::MobilePayRedirect(_) + | WalletData::PaypalSdk(_) + | WalletData::Paze(_) + | WalletData::SamsungPay(_) + | WalletData::TwintRedirect {} + | WalletData::VippsRedirect {} + | WalletData::TouchNGoRedirect(_) + | WalletData::CashappQr(_) + | WalletData::SwishQr(_) + | WalletData::WeChatPayQr(_) + | WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Zen"), ))?, }; @@ -511,7 +523,7 @@ impl amount, terminal_uuid: Secret::new(terminal_uuid), signature: None, - url_redirect: item.router_data.request.get_return_url()?, + url_redirect: item.router_data.request.get_router_return_url()?, }; checkout_request.signature = Some(get_checkout_signature(&checkout_request, &session_data)?); @@ -612,11 +624,14 @@ fn get_item_object( name: data.product_name.clone(), quantity: data.quantity, price: utils::to_currency_base_unit_with_zero_decimal_check( - data.amount, + data.amount.get_amount_as_i64(), // This should be changed to MinorUnit when we implement amount conversion for this connector. Additionally, the function get_amount_as_i64() should be avoided in the future. item.request.currency, )?, line_amount_total: (f64::from(data.quantity) - * utils::to_currency_base_unit_asf64(data.amount, item.request.currency)?) + * utils::to_currency_base_unit_asf64( + data.amount.get_amount_as_i64(), // This should be changed to MinorUnit when we implement amount conversion for this connector. Additionally, the function get_amount_as_i64() should be avoided in the future. + item.request.currency, + )?) .to_string(), }) }) @@ -624,7 +639,7 @@ fn get_item_object( } fn get_browser_details( - browser_info: &types::BrowserInformation, + browser_info: &BrowserInformation, ) -> CustomResult { let screen_height = browser_info .screen_height @@ -668,35 +683,29 @@ impl TryFrom<&ZenRouterData<&types::PaymentsAuthorizeRouterData>> for ZenPayment item: &ZenRouterData<&types::PaymentsAuthorizeRouterData>, ) -> Result { match &item.router_data.request.payment_method_data { - domain::PaymentMethodData::Card(card) => Self::try_from((item, card)), - domain::PaymentMethodData::Wallet(wallet_data) => Self::try_from((item, wallet_data)), - domain::PaymentMethodData::Voucher(voucher_data) => { - Self::try_from((item, voucher_data)) - } - domain::PaymentMethodData::BankTransfer(bank_transfer_data) => { + PaymentMethodData::Card(card) => Self::try_from((item, card)), + PaymentMethodData::Wallet(wallet_data) => Self::try_from((item, wallet_data)), + PaymentMethodData::Voucher(voucher_data) => Self::try_from((item, voucher_data)), + PaymentMethodData::BankTransfer(bank_transfer_data) => { Self::try_from((item, bank_transfer_data)) } - domain::PaymentMethodData::BankRedirect(bank_redirect_data) => { + PaymentMethodData::BankRedirect(bank_redirect_data) => { Self::try_from(bank_redirect_data) } - domain::PaymentMethodData::PayLater(paylater_data) => Self::try_from(paylater_data), - domain::PaymentMethodData::BankDebit(bank_debit_data) => { - Self::try_from(bank_debit_data) - } - domain::PaymentMethodData::CardRedirect(car_redirect_data) => { - Self::try_from(car_redirect_data) - } - domain::PaymentMethodData::GiftCard(gift_card_data) => { - Self::try_from(gift_card_data.as_ref()) - } - domain::PaymentMethodData::Crypto(_) - | domain::PaymentMethodData::MandatePayment - | domain::PaymentMethodData::Reward - | domain::PaymentMethodData::RealTimePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + PaymentMethodData::PayLater(paylater_data) => Self::try_from(paylater_data), + PaymentMethodData::BankDebit(bank_debit_data) => Self::try_from(bank_debit_data), + PaymentMethodData::CardRedirect(car_redirect_data) => Self::try_from(car_redirect_data), + PaymentMethodData::GiftCard(gift_card_data) => Self::try_from(gift_card_data.as_ref()), + PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Zen"), ))? @@ -705,28 +714,28 @@ impl TryFrom<&ZenRouterData<&types::PaymentsAuthorizeRouterData>> for ZenPayment } } -impl TryFrom<&domain::BankRedirectData> for ZenPaymentsRequest { +impl TryFrom<&BankRedirectData> for ZenPaymentsRequest { type Error = error_stack::Report; - fn try_from(value: &domain::BankRedirectData) -> Result { + fn try_from(value: &BankRedirectData) -> Result { match value { - domain::BankRedirectData::Ideal { .. } - | domain::BankRedirectData::Sofort { .. } - | domain::BankRedirectData::BancontactCard { .. } - | domain::BankRedirectData::Blik { .. } - | domain::BankRedirectData::Trustly { .. } - | domain::BankRedirectData::Eps { .. } - | domain::BankRedirectData::Giropay { .. } - | domain::BankRedirectData::Przelewy24 { .. } - | domain::BankRedirectData::Bizum {} - | domain::BankRedirectData::Interac { .. } - | domain::BankRedirectData::OnlineBankingCzechRepublic { .. } - | domain::BankRedirectData::OnlineBankingFinland { .. } - | domain::BankRedirectData::OnlineBankingPoland { .. } - | domain::BankRedirectData::OnlineBankingSlovakia { .. } - | domain::BankRedirectData::OpenBankingUk { .. } - | domain::BankRedirectData::OnlineBankingFpx { .. } - | domain::BankRedirectData::OnlineBankingThailand { .. } - | domain::BankRedirectData::LocalBankRedirect {} => { + BankRedirectData::Ideal { .. } + | BankRedirectData::Sofort { .. } + | BankRedirectData::BancontactCard { .. } + | BankRedirectData::Blik { .. } + | BankRedirectData::Trustly { .. } + | BankRedirectData::Eps { .. } + | BankRedirectData::Giropay { .. } + | BankRedirectData::Przelewy24 { .. } + | BankRedirectData::Bizum {} + | BankRedirectData::Interac { .. } + | BankRedirectData::OnlineBankingCzechRepublic { .. } + | BankRedirectData::OnlineBankingFinland { .. } + | BankRedirectData::OnlineBankingPoland { .. } + | BankRedirectData::OnlineBankingSlovakia { .. } + | BankRedirectData::OpenBankingUk { .. } + | BankRedirectData::OnlineBankingFpx { .. } + | BankRedirectData::OnlineBankingThailand { .. } + | BankRedirectData::LocalBankRedirect {} => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Zen"), ) @@ -736,66 +745,61 @@ impl TryFrom<&domain::BankRedirectData> for ZenPaymentsRequest { } } -impl TryFrom<&domain::payments::PayLaterData> for ZenPaymentsRequest { +impl TryFrom<&PayLaterData> for ZenPaymentsRequest { type Error = error_stack::Report; - fn try_from(value: &domain::payments::PayLaterData) -> Result { + fn try_from(value: &PayLaterData) -> Result { match value { - domain::payments::PayLaterData::KlarnaRedirect { .. } - | domain::payments::PayLaterData::KlarnaSdk { .. } - | domain::payments::PayLaterData::AffirmRedirect {} - | domain::payments::PayLaterData::AfterpayClearpayRedirect { .. } - | domain::payments::PayLaterData::PayBrightRedirect {} - | domain::payments::PayLaterData::WalleyRedirect {} - | domain::payments::PayLaterData::AlmaRedirect {} - | domain::payments::PayLaterData::AtomeRedirect {} => { - Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("Zen"), - ) - .into()) - } + PayLaterData::KlarnaRedirect { .. } + | PayLaterData::KlarnaSdk { .. } + | PayLaterData::KlarnaCheckout {} + | PayLaterData::AffirmRedirect {} + | PayLaterData::AfterpayClearpayRedirect { .. } + | PayLaterData::PayBrightRedirect {} + | PayLaterData::WalleyRedirect {} + | PayLaterData::AlmaRedirect {} + | PayLaterData::AtomeRedirect {} => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Zen"), + ) + .into()), } } } -impl TryFrom<&domain::BankDebitData> for ZenPaymentsRequest { +impl TryFrom<&BankDebitData> for ZenPaymentsRequest { type Error = error_stack::Report; - fn try_from(value: &domain::BankDebitData) -> Result { + fn try_from(value: &BankDebitData) -> Result { match value { - domain::BankDebitData::AchBankDebit { .. } - | domain::BankDebitData::SepaBankDebit { .. } - | domain::BankDebitData::BecsBankDebit { .. } - | domain::BankDebitData::BacsBankDebit { .. } => { - Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("Zen"), - ) - .into()) - } + BankDebitData::AchBankDebit { .. } + | BankDebitData::SepaBankDebit { .. } + | BankDebitData::BecsBankDebit { .. } + | BankDebitData::BacsBankDebit { .. } => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Zen"), + ) + .into()), } } } -impl TryFrom<&domain::payments::CardRedirectData> for ZenPaymentsRequest { +impl TryFrom<&CardRedirectData> for ZenPaymentsRequest { type Error = error_stack::Report; - fn try_from(value: &domain::payments::CardRedirectData) -> Result { + fn try_from(value: &CardRedirectData) -> Result { match value { - domain::payments::CardRedirectData::Knet {} - | domain::payments::CardRedirectData::Benefit {} - | domain::payments::CardRedirectData::MomoAtm {} - | domain::payments::CardRedirectData::CardRedirect {} => { - Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("Zen"), - ) - .into()) - } + CardRedirectData::Knet {} + | CardRedirectData::Benefit {} + | CardRedirectData::MomoAtm {} + | CardRedirectData::CardRedirect {} => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Zen"), + ) + .into()), } } } -impl TryFrom<&domain::GiftCardData> for ZenPaymentsRequest { +impl TryFrom<&GiftCardData> for ZenPaymentsRequest { type Error = error_stack::Report; - fn try_from(value: &domain::GiftCardData) -> Result { + fn try_from(value: &GiftCardData) -> Result { match value { - domain::GiftCardData::PaySafeCard {} | domain::GiftCardData::Givex(_) => { + GiftCardData::PaySafeCard {} | GiftCardData::Givex(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Zen"), ) @@ -877,29 +881,24 @@ pub struct ZenMerchantActionData { redirect_url: url::Url, } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { match item.response { - ZenPaymentsResponse::ApiResponse(response) => { - Self::try_from(types::ResponseRouterData { - response, - data: item.data, - http_code: item.http_code, - }) - } - ZenPaymentsResponse::CheckoutResponse(response) => { - Self::try_from(types::ResponseRouterData { - response, - data: item.data, - http_code: item.http_code, - }) - } + ZenPaymentsResponse::ApiResponse(response) => Self::try_from(ResponseRouterData { + response, + data: item.data, + http_code: item.http_code, + }), + ZenPaymentsResponse::CheckoutResponse(response) => Self::try_from(ResponseRouterData { + response, + data: item.data, + http_code: item.http_code, + }), } } } @@ -910,14 +909,14 @@ fn get_zen_response( ) -> CustomResult< ( enums::AttemptStatus, - Option, - types::PaymentsResponseData, + Option, + PaymentsResponseData, ), errors::ConnectorError, > { let redirection_data_action = response.merchant_action.map(|merchant_action| { ( - services::RedirectForm::from((merchant_action.data.redirect_url, Method::Get)), + RedirectForm::from((merchant_action.data.redirect_url, Method::Get)), merchant_action.action, ) }); @@ -927,14 +926,14 @@ fn get_zen_response( }; let status = enums::AttemptStatus::foreign_try_from((response.status, action))?; let error = if utils::is_payment_failure(status) { - Some(types::ErrorResponse { + Some(ErrorResponse { code: response .reject_code - .unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()), + .unwrap_or_else(|| NO_ERROR_CODE.to_string()), message: response .reject_reason .clone() - .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), + .unwrap_or_else(|| NO_ERROR_MESSAGE.to_string()), reason: response.reject_reason, status_code, attempt_status: Some(status), @@ -943,10 +942,10 @@ fn get_zen_response( } else { None }; - let payment_response_data = types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(response.id.clone()), - redirection_data, - mandate_reference: None, + let payment_response_data = PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(response.id.clone()), + redirection_data: Box::new(redirection_data), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -956,12 +955,12 @@ fn get_zen_response( Ok((status, error, payment_response_data)) } -impl TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - value: types::ResponseRouterData, + value: ResponseRouterData, ) -> Result { let (status, error, payment_response_data) = get_zen_response(value.response.clone(), value.http_code)?; @@ -974,23 +973,23 @@ impl TryFrom TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - value: types::ResponseRouterData, + value: ResponseRouterData, ) -> Result { - let redirection_data = Some(services::RedirectForm::from(( + let redirection_data = Some(RedirectForm::from(( value.response.redirect_url, Method::Get, ))); Ok(Self { status: enums::AttemptStatus::AuthenticationPending, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::NoResponseId, - redirection_data, - mandate_reference: None, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::NoResponseId, + redirection_data: Box::new(redirection_data), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -1052,12 +1051,12 @@ pub struct RefundResponse { reject_reason: Option, } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { let (error, refund_response_data) = get_zen_refund_response(item.response, item.http_code)?; Ok(Self { @@ -1070,18 +1069,17 @@ impl TryFrom> fn get_zen_refund_response( response: RefundResponse, status_code: u16, -) -> CustomResult<(Option, types::RefundsResponseData), errors::ConnectorError> -{ +) -> CustomResult<(Option, RefundsResponseData), errors::ConnectorError> { let refund_status = enums::RefundStatus::from(response.status); let error = if utils::is_refund_failure(refund_status) { - Some(types::ErrorResponse { + Some(ErrorResponse { code: response .reject_code - .unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()), + .unwrap_or_else(|| NO_ERROR_CODE.to_string()), message: response .reject_reason .clone() - .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), + .unwrap_or_else(|| NO_ERROR_MESSAGE.to_string()), reason: response.reject_reason, status_code, attempt_status: None, @@ -1090,23 +1088,21 @@ fn get_zen_refund_response( } else { None }; - let refund_response_data = types::RefundsResponseData { + let refund_response_data = RefundsResponseData { connector_refund_id: response.id, refund_status, }; Ok((error, refund_response_data)) } -impl TryFrom> - for types::RefundsRouterData -{ +impl TryFrom> for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { let refund_status = enums::RefundStatus::from(item.response.status); Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item.response.id, refund_status, }), diff --git a/crates/router/src/connector/zsl.rs b/crates/hyperswitch_connectors/src/connectors/zsl.rs similarity index 57% rename from crates/router/src/connector/zsl.rs rename to crates/hyperswitch_connectors/src/connectors/zsl.rs index d2341a510e11..434cfbc3435a 100644 --- a/crates/router/src/connector/zsl.rs +++ b/crates/hyperswitch_connectors/src/connectors/zsl.rs @@ -2,30 +2,55 @@ pub mod transformers; use std::fmt::Debug; -use common_utils::ext_traits::ValueExt; -use diesel_models::enums; +use api_models::webhooks::{IncomingWebhookEvent, ObjectReferenceId}; +use common_enums::enums; +use common_utils::{ + errors::CustomResult, + ext_traits::{BytesExt, ValueExt}, + request::{Method, Request, RequestBuilder, RequestContent}, +}; use error_stack::ResultExt; -use masking::{ExposeInterface, Secret}; -use transformers as zsl; - -use crate::{ - configs::settings, - connector::utils as connector_utils, - core::errors::{self, CustomResult}, - events::connector_api_logs::ConnectorEvent, - headers, - services::{ - self, - request::{self}, - ConnectorIntegration, ConnectorValidation, +use hyperswitch_domain_models::{ + api::ApplicationResponse, + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{ + ConnectorInfo, PaymentMethodDetails, PaymentsResponseData, RefundsResponseData, + SupportedPaymentMethods, SupportedPaymentMethodsExt, }, types::{ - self, - api::{self, ConnectorCommon, ConnectorCommonExt}, - transformers::ForeignFrom, - ErrorResponse, RequestContent, Response, + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsSessionRouterData, PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, + TokenizationRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, }, - utils::BytesExt, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks::{IncomingWebhook, IncomingWebhookFlowError, IncomingWebhookRequestDetails}, +}; +use lazy_static::lazy_static; +use masking::{ExposeInterface, Secret}; +use transformers::{self as zsl, get_status}; + +use crate::{ + constants::headers, + types::{RefreshTokenRouterData, ResponseRouterData}, }; #[derive(Debug, Clone)] @@ -50,9 +75,9 @@ where { fn build_headers( &self, - _req: &types::RouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let header = vec![( headers::CONTENT_TYPE.to_string(), self.get_content_type().to_string().into(), @@ -74,7 +99,7 @@ impl ConnectorCommon for Zsl { "application/x-www-form-urlencoded" } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.zsl.base_url.as_ref() } @@ -103,35 +128,17 @@ impl ConnectorCommon for Zsl { } impl ConnectorValidation for Zsl { - fn validate_capture_method( - &self, - capture_method: Option, - _pmt: Option, - ) -> CustomResult<(), errors::ConnectorError> { - let capture_method = capture_method.unwrap_or_default(); - match capture_method { - enums::CaptureMethod::Automatic => Ok(()), - enums::CaptureMethod::Manual - | enums::CaptureMethod::ManualMultiple - | enums::CaptureMethod::Scheduled => Err( - connector_utils::construct_not_supported_error_report(capture_method, self.id()), - ), - } - } - fn is_webhook_source_verification_mandatory(&self) -> bool { true } } -impl ConnectorIntegration - for Zsl -{ +impl ConnectorIntegration for Zsl { fn get_headers( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -141,16 +148,16 @@ impl ConnectorIntegration CustomResult { Ok(format!("{}ecp", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::PaymentsAuthorizeRouterData, - _connectors: &settings::Connectors, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_router_data = zsl::ZslRouterData::try_from(( &self.get_currency_unit(), @@ -164,12 +171,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsAuthorizeType::get_url( self, req, connectors, )?) @@ -186,17 +193,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response = serde_urlencoded::from_bytes::(&res.response) .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i: &mut ConnectorEvent| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -220,15 +227,13 @@ impl ConnectorIntegration - for Zsl -{ +impl ConnectorIntegration for Zsl { fn handle_response( &self, - data: &types::PaymentsSyncRouterData, + data: &PaymentsSyncRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: zsl::ZslWebhookResponse = res .response .parse_struct("ZslWebhookResponse") @@ -237,7 +242,7 @@ impl ConnectorIntegration - for Zsl -{ +impl ConnectorIntegration for Zsl { fn build_request( &self, - _req: &types::PaymentsSessionRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &PaymentsSessionRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err(errors::ConnectorError::NotSupported { message: "Session flow".to_owned(), connector: "Zsl", @@ -261,18 +264,14 @@ impl ConnectorIntegration for Zsl +impl ConnectorIntegration + for Zsl { fn build_request( &self, - _req: &types::TokenizationRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &TokenizationRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err(errors::ConnectorError::NotSupported { message: "PaymentMethod Tokenization flow ".to_owned(), connector: "Zsl", @@ -281,14 +280,12 @@ impl } } -impl ConnectorIntegration - for Zsl -{ +impl ConnectorIntegration for Zsl { fn build_request( &self, - _req: &types::RefreshTokenRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &RefreshTokenRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err(errors::ConnectorError::NotSupported { message: "AccessTokenAuth flow".to_owned(), connector: "Zsl", @@ -297,22 +294,12 @@ impl ConnectorIntegration for Zsl -{ +impl ConnectorIntegration for Zsl { fn build_request( &self, - _req: &types::RouterData< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - >, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err(errors::ConnectorError::NotSupported { message: "SetupMandate flow".to_owned(), connector: "Zsl", @@ -321,14 +308,12 @@ impl } } -impl ConnectorIntegration - for Zsl -{ +impl ConnectorIntegration for Zsl { fn build_request( &self, - _req: &types::PaymentsCaptureRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err(errors::ConnectorError::NotSupported { message: "Capture flow".to_owned(), connector: "Zsl", @@ -337,14 +322,12 @@ impl ConnectorIntegration - for Zsl -{ +impl ConnectorIntegration for Zsl { fn build_request( &self, - _req: &types::PaymentsCancelRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &PaymentsCancelRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err(errors::ConnectorError::NotSupported { message: "Void flow ".to_owned(), connector: "Zsl", @@ -353,12 +336,12 @@ impl ConnectorIntegration for Zsl { +impl ConnectorIntegration for Zsl { fn build_request( &self, - _req: &types::RefundsRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err(errors::ConnectorError::NotSupported { message: "Refund flow".to_owned(), connector: "Zsl", @@ -367,12 +350,12 @@ impl ConnectorIntegration for Zsl { +impl ConnectorIntegration for Zsl { fn build_request( &self, - _req: &types::RefundSyncRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &RefundSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err(errors::ConnectorError::NotSupported { message: "Rsync flow ".to_owned(), connector: "Zsl", @@ -382,33 +365,31 @@ impl ConnectorIntegration, - ) -> CustomResult { + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { let notif = get_webhook_object_from_body(request.body) .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; - Ok(api_models::webhooks::ObjectReferenceId::PaymentId( + Ok(ObjectReferenceId::PaymentId( api_models::payments::PaymentIdType::PaymentAttemptId(notif.mer_ref), )) } fn get_webhook_event_type( &self, - request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { let notif = get_webhook_object_from_body(request.body) .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?; - Ok(api_models::webhooks::IncomingWebhookEvent::foreign_from( - notif.status, - )) + Ok(get_status(notif.status)) } fn get_webhook_resource_object( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { let response = get_webhook_object_from_body(request.body) .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?; @@ -417,14 +398,14 @@ impl api::IncomingWebhook for Zsl { async fn verify_webhook_source( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, _merchant_id: &common_utils::id_type::MerchantId, _connector_webhook_details: Option, connector_account_details: common_utils::crypto::Encryptable>, _connector_label: &str, ) -> CustomResult { let connector_account_details = connector_account_details - .parse_value::("ConnectorAuthType") + .parse_value::("ConnectorAuthType") .change_context_lazy(|| errors::ConnectorError::WebhookSourceVerificationFailed)?; let auth_type = zsl::ZslAuthType::try_from(&connector_account_details)?; let key = auth_type.api_key.expose(); @@ -449,12 +430,10 @@ impl api::IncomingWebhook for Zsl { fn get_webhook_api_response( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult, errors::ConnectorError> - { - Ok(services::api::ApplicationResponse::TextPlain( - "CALLBACK-OK".to_string(), - )) + _request: &IncomingWebhookRequestDetails<'_>, + _error_kind: Option, + ) -> CustomResult, errors::ConnectorError> { + Ok(ApplicationResponse::TextPlain("CALLBACK-OK".to_string())) } } @@ -466,3 +445,46 @@ fn get_webhook_object_from_body( .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; Ok(response) } + +lazy_static! { + static ref ZSL_SUPPORTED_PAYMENT_METHODS: SupportedPaymentMethods = { + let supported_capture_methods = vec![enums::CaptureMethod::Automatic]; + + let mut zsl_supported_payment_methods = SupportedPaymentMethods::new(); + zsl_supported_payment_methods.add( + enums::PaymentMethod::BankTransfer, + enums::PaymentMethodType::LocalBankTransfer, + PaymentMethodDetails{ + mandates: common_enums::FeatureStatus::NotSupported, + refunds: common_enums::FeatureStatus::NotSupported, + supported_capture_methods, + }, + ); + + zsl_supported_payment_methods + }; + + static ref ZSL_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo { + description: + "Zsl is a payment gateway operating in China, specializing in facilitating local bank transfers" + .to_string(), + connector_type: enums::PaymentConnectorCategory::PaymentGateway, + }; + + static ref ZSL_SUPPORTED_WEBHOOK_FLOWS: Vec = Vec::new(); + +} + +impl ConnectorSpecifications for Zsl { + fn get_connector_about(&self) -> Option<&'static ConnectorInfo> { + Some(&*ZSL_CONNECTOR_INFO) + } + + fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> { + Some(&*ZSL_SUPPORTED_PAYMENT_METHODS) + } + + fn get_supported_webhook_flows(&self) -> Option<&'static [enums::EventClass]> { + Some(&*ZSL_SUPPORTED_WEBHOOK_FLOWS) + } +} diff --git a/crates/router/src/connector/zsl/transformers.rs b/crates/hyperswitch_connectors/src/connectors/zsl/transformers.rs similarity index 83% rename from crates/router/src/connector/zsl/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/zsl/transformers.rs index b3e7bfb3bfdc..b2ba05b7cdec 100644 --- a/crates/router/src/connector/zsl/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/zsl/transformers.rs @@ -1,18 +1,27 @@ use std::collections::HashMap; use base64::Engine; -use common_utils::{crypto::GenerateDigest, date_time, pii::Email}; +use common_enums::enums; +use common_utils::{crypto::GenerateDigest, date_time, pii::Email, request::Method}; use error_stack::ResultExt; +use hyperswitch_domain_models::{ + payment_method_data::{BankTransferData, PaymentMethodData}, + router_data::{ConnectorAuthType, ErrorResponse, RouterData}, + router_request_types::{PaymentsSyncData, ResponseId}, + router_response_types::{PaymentsResponseData, RedirectForm}, + types, +}; +use hyperswitch_interfaces::{api, consts::NO_ERROR_CODE, errors}; use masking::{ExposeInterface, Secret}; use ring::digest; use serde::{Deserialize, Serialize}; use crate::{ - connector::utils::{self as connector_utils, PaymentsAuthorizeRequestData, RouterData}, - consts, - core::errors, - services, - types::{self, domain, storage::enums}, + types::ResponseRouterData, + utils::{ + get_amount_as_string, get_unimplemented_payment_method_error_message, + PaymentsAuthorizeRequestData, RouterData as _, + }, }; mod auth_error { @@ -27,17 +36,12 @@ pub struct ZslRouterData { pub router_data: T, } -impl TryFrom<(&types::api::CurrencyUnit, enums::Currency, i64, T)> for ZslRouterData { +impl TryFrom<(&api::CurrencyUnit, enums::Currency, i64, T)> for ZslRouterData { type Error = error_stack::Report; fn try_from( - (currency_unit, currency, txn_amount, item): ( - &types::api::CurrencyUnit, - enums::Currency, - i64, - T, - ), + (currency_unit, currency, txn_amount, item): (&api::CurrencyUnit, enums::Currency, i64, T), ) -> Result { - let amount = connector_utils::get_amount_as_string(currency_unit, txn_amount, currency)?; + let amount = get_amount_as_string(currency_unit, txn_amount, currency)?; Ok(Self { amount, router_data: item, @@ -50,11 +54,11 @@ pub struct ZslAuthType { pub(super) merchant_id: Secret, } -impl TryFrom<&types::ConnectorAuthType> for ZslAuthType { +impl TryFrom<&ConnectorAuthType> for ZslAuthType { type Error = error_stack::Report; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { + fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - types::ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { + ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { api_key: api_key.to_owned(), merchant_id: key1.clone(), }), @@ -139,57 +143,51 @@ impl TryFrom<&ZslRouterData<&types::PaymentsAuthorizeRouterData>> for ZslPayment item: &ZslRouterData<&types::PaymentsAuthorizeRouterData>, ) -> Result { let payment_method = match item.router_data.request.payment_method_data.clone() { - domain::PaymentMethodData::BankTransfer(bank_transfer_data) => { - match *bank_transfer_data { - domain::BankTransferData::LocalBankTransfer { bank_code } => Ok( - ZslPaymentMethods::LocalBankTransfer(LocalBankTransaferRequest { - bank_code, - pay_method: None, - }), - ), - domain::BankTransferData::AchBankTransfer { .. } - | domain::BankTransferData::SepaBankTransfer { .. } - | domain::BankTransferData::BacsBankTransfer { .. } - | domain::BankTransferData::MultibancoBankTransfer { .. } - | domain::BankTransferData::PermataBankTransfer { .. } - | domain::BankTransferData::BcaBankTransfer { .. } - | domain::BankTransferData::BniVaBankTransfer { .. } - | domain::BankTransferData::BriVaBankTransfer { .. } - | domain::BankTransferData::CimbVaBankTransfer { .. } - | domain::BankTransferData::DanamonVaBankTransfer { .. } - | domain::BankTransferData::MandiriVaBankTransfer { .. } - | domain::BankTransferData::Pix { .. } - | domain::BankTransferData::Pse {} => { - Err(errors::ConnectorError::NotImplemented( - connector_utils::get_unimplemented_payment_method_error_message( - item.router_data.connector.as_str(), - ), - )) - } - } - } - domain::PaymentMethodData::Card(_) - | domain::PaymentMethodData::CardRedirect(_) - | domain::PaymentMethodData::Wallet(_) - | domain::PaymentMethodData::PayLater(_) - | domain::PaymentMethodData::BankRedirect(_) - | domain::PaymentMethodData::BankDebit(_) - | domain::PaymentMethodData::Crypto(_) - | domain::PaymentMethodData::MandatePayment - | domain::PaymentMethodData::Reward - | domain::PaymentMethodData::RealTimePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) - | domain::PaymentMethodData::OpenBanking(_) => { - Err(errors::ConnectorError::NotImplemented( - connector_utils::get_unimplemented_payment_method_error_message( + PaymentMethodData::BankTransfer(bank_transfer_data) => match *bank_transfer_data { + BankTransferData::LocalBankTransfer { bank_code } => Ok( + ZslPaymentMethods::LocalBankTransfer(LocalBankTransaferRequest { + bank_code, + pay_method: None, + }), + ), + BankTransferData::AchBankTransfer { .. } + | BankTransferData::SepaBankTransfer { .. } + | BankTransferData::BacsBankTransfer { .. } + | BankTransferData::MultibancoBankTransfer { .. } + | BankTransferData::PermataBankTransfer { .. } + | BankTransferData::BcaBankTransfer { .. } + | BankTransferData::BniVaBankTransfer { .. } + | BankTransferData::BriVaBankTransfer { .. } + | BankTransferData::CimbVaBankTransfer { .. } + | BankTransferData::DanamonVaBankTransfer { .. } + | BankTransferData::MandiriVaBankTransfer { .. } + | BankTransferData::Pix { .. } + | BankTransferData::Pse {} => Err(errors::ConnectorError::NotImplemented( + get_unimplemented_payment_method_error_message( item.router_data.connector.as_str(), ), - )) - } + )), + }, + PaymentMethodData::Card(_) + | PaymentMethodData::CardRedirect(_) + | PaymentMethodData::Wallet(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::OpenBanking(_) => Err(errors::ConnectorError::NotImplemented( + get_unimplemented_payment_method_error_message(item.router_data.connector.as_str()), + )), }?; let auth_type = ZslAuthType::try_from(&item.router_data.connector_auth_type)?; let key: Secret = auth_type.api_key; @@ -292,13 +290,12 @@ pub struct ZslPaymentsResponse { signature: Secret, } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { if item.response.status.eq("0") && !item.response.txn_url.is_empty() { let auth_type = ZslAuthType::try_from(&item.data.connector_auth_type)?; @@ -325,14 +322,14 @@ impl Ok(Self { status: enums::AttemptStatus::AuthenticationPending, // Redirect is always expected after success response - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::NoResponseId, - redirection_data: Some(services::RedirectForm::Form { + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::NoResponseId, + redirection_data: Box::new(Some(RedirectForm::Form { endpoint: redirect_url, - method: services::Method::Get, + method: Method::Get, form_fields: HashMap::new(), - }), - mandate_reference: None, + })), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(item.response.mer_ref.clone()), @@ -345,8 +342,8 @@ impl // When the signature check fails Ok(Self { status: enums::AttemptStatus::Failure, - response: Err(types::ErrorResponse { - code: consts::NO_ERROR_CODE.to_string(), + response: Err(ErrorResponse { + code: NO_ERROR_CODE.to_string(), message: auth_error::INVALID_SIGNATURE.to_string(), reason: Some(auth_error::INVALID_SIGNATURE.to_string()), status_code: item.http_code, @@ -361,7 +358,7 @@ impl ZslResponseStatus::try_from(item.response.status.clone())?.to_string(); Ok(Self { status: enums::AttemptStatus::Failure, - response: Err(types::ErrorResponse { + response: Err(ErrorResponse { code: item.response.status.clone(), message: error_reason.clone(), reason: Some(error_reason.clone()), @@ -397,61 +394,35 @@ pub struct ZslWebhookResponse { pub signature: Secret, } -impl types::transformers::ForeignFrom for api_models::webhooks::IncomingWebhookEvent { - fn foreign_from(status: String) -> Self { - match status.as_str() { - //any response with status != 0 are a failed deposit transaction - "0" => Self::PaymentIntentSuccess, - _ => Self::PaymentIntentFailure, - } +pub(crate) fn get_status(status: String) -> api_models::webhooks::IncomingWebhookEvent { + match status.as_str() { + //any response with status != 0 are a failed deposit transaction + "0" => api_models::webhooks::IncomingWebhookEvent::PaymentIntentSuccess, + _ => api_models::webhooks::IncomingWebhookEvent::PaymentIntentFailure, } } -impl - TryFrom< - types::ResponseRouterData< - F, - ZslWebhookResponse, - types::PaymentsSyncData, - types::PaymentsResponseData, - >, - > for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< - F, - ZslWebhookResponse, - types::PaymentsSyncData, - types::PaymentsResponseData, - >, + item: ResponseRouterData, ) -> Result { let paid_amount = item .response .consr_paid_amt .parse::() .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - let txn_amount = item - .response - .txn_amt - .parse::() - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - let status = if txn_amount > paid_amount { - enums::AttemptStatus::PartialCharged - } else { - enums::AttemptStatus::Charged - }; if item.response.status == "0" { Ok(Self { - status, + status: enums::AttemptStatus::Charged, amount_captured: Some(paid_amount), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - item.response.txn_id.clone(), - ), - redirection_data: None, - mandate_reference: None, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.txn_id.clone()), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(item.response.mer_ref.clone()), @@ -465,7 +436,7 @@ impl ZslResponseStatus::try_from(item.response.status.clone())?.to_string(); Ok(Self { status: enums::AttemptStatus::Failure, - response: Err(types::ErrorResponse { + response: Err(ErrorResponse { code: item.response.status.clone(), message: error_reason.clone(), reason: Some(error_reason.clone()), diff --git a/crates/hyperswitch_connectors/src/constants.rs b/crates/hyperswitch_connectors/src/constants.rs index 5b71a1bd29c8..3d99f8d167ff 100644 --- a/crates/hyperswitch_connectors/src/constants.rs +++ b/crates/hyperswitch_connectors/src/constants.rs @@ -1,6 +1,8 @@ /// Header Constants pub(crate) mod headers { + pub(crate) const ACCEPT: &str = "Accept"; pub(crate) const API_KEY: &str = "API-KEY"; + pub(crate) const APIKEY: &str = "apikey"; pub(crate) const API_TOKEN: &str = "Api-Token"; pub(crate) const AUTHORIZATION: &str = "Authorization"; pub(crate) const CONTENT_TYPE: &str = "Content-Type"; @@ -8,7 +10,10 @@ pub(crate) mod headers { pub(crate) const IDEMPOTENCY_KEY: &str = "Idempotency-Key"; pub(crate) const MESSAGE_SIGNATURE: &str = "Message-Signature"; pub(crate) const MERCHANT_ID: &str = "Merchant-ID"; + pub(crate) const REQUEST_ID: &str = "request-id"; + pub(crate) const NONCE: &str = "nonce"; pub(crate) const TIMESTAMP: &str = "Timestamp"; + pub(crate) const TOKEN: &str = "token"; pub(crate) const X_ACCEPT_VERSION: &str = "X-Accept-Version"; pub(crate) const X_CC_API_KEY: &str = "X-CC-Api-Key"; pub(crate) const X_CC_VERSION: &str = "X-CC-Version"; @@ -19,4 +24,11 @@ pub(crate) mod headers { pub(crate) const X_RANDOM_VALUE: &str = "X-RandomValue"; pub(crate) const X_REQUEST_DATE: &str = "X-RequestDate"; pub(crate) const X_VERSION: &str = "X-Version"; + pub(crate) const X_API_KEY: &str = "X-Api-Key"; + pub(crate) const CORRELATION_ID: &str = "Correlation-Id"; + pub(crate) const WP_API_VERSION: &str = "WP-Api-Version"; + pub(crate) const SOURCE: &str = "Source"; } + +/// Unsupported response type error message +pub const UNSUPPORTED_ERROR_MESSAGE: &str = "Unsupported response type"; diff --git a/crates/hyperswitch_connectors/src/default_implementations.rs b/crates/hyperswitch_connectors/src/default_implementations.rs index 0d43c263f97e..703b0bb2ac7e 100644 --- a/crates/hyperswitch_connectors/src/default_implementations.rs +++ b/crates/hyperswitch_connectors/src/default_implementations.rs @@ -29,18 +29,23 @@ use hyperswitch_domain_models::{ mandate_revoke::MandateRevoke, payments::{ Approve, AuthorizeSessionToken, CalculateTax, CompleteAuthorize, - CreateConnectorCustomer, IncrementalAuthorization, PostProcessing, PreProcessing, - Reject, SdkSessionUpdate, + CreateConnectorCustomer, IncrementalAuthorization, PostProcessing, PostSessionTokens, + PreProcessing, Reject, SdkSessionUpdate, }, webhooks::VerifyWebhookSource, + PostAuthenticate, PreAuthenticate, }, router_request_types::{ + unified_authentication_service::{ + UasAuthenticationResponseData, UasPostAuthenticationRequestData, + UasPreAuthenticationRequestData, + }, AcceptDisputeRequestData, AuthorizeSessionTokenData, CompleteAuthorizeData, ConnectorCustomerData, DefendDisputeRequestData, MandateRevokeRequestData, PaymentsApproveData, PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, - PaymentsPreProcessingData, PaymentsRejectData, PaymentsTaxCalculationData, - RetrieveFileRequestData, SdkPaymentsSessionUpdateData, SubmitEvidenceRequestData, - UploadFileRequestData, VerifyWebhookSourceRequestData, + PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsRejectData, + PaymentsTaxCalculationData, RetrieveFileRequestData, SdkPaymentsSessionUpdateData, + SubmitEvidenceRequestData, UploadFileRequestData, VerifyWebhookSourceRequestData, }, router_response_types::{ AcceptDisputeResponse, DefendDisputeResponse, MandateRevokeResponseData, @@ -65,11 +70,12 @@ use hyperswitch_interfaces::{ files::{FileUpload, RetrieveFile, UploadFile}, payments::{ ConnectorCustomer, PaymentApprove, PaymentAuthorizeSessionToken, - PaymentIncrementalAuthorization, PaymentReject, PaymentSessionUpdate, - PaymentsCompleteAuthorize, PaymentsPostProcessing, PaymentsPreProcessing, - TaxCalculation, + PaymentIncrementalAuthorization, PaymentPostSessionTokens, PaymentReject, + PaymentSessionUpdate, PaymentsCompleteAuthorize, PaymentsPostProcessing, + PaymentsPreProcessing, TaxCalculation, }, ConnectorIntegration, ConnectorMandateRevoke, ConnectorRedirectResponse, + UasPostAuthentication, UasPreAuthentication, UnifiedAuthenticationService, }, errors::ConnectorError, }; @@ -89,28 +95,59 @@ macro_rules! default_imp_for_authorize_session_token { } default_imp_for_authorize_session_token!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Taxjar, + connectors::UnifiedAuthenticationService, connectors::Volt, connectors::Thunes, connectors::Tsys, - connectors::Worldline + connectors::Worldline, + connectors::Worldpay, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); macro_rules! default_imp_for_calculate_tax { @@ -128,28 +165,59 @@ macro_rules! default_imp_for_calculate_tax { } default_imp_for_calculate_tax!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, + connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, + connectors::Fiuu, + connectors::Forte, + connectors::Globepay, + connectors::Gocardless, connectors::Helcim, - connectors::Stax, - connectors::Square, - connectors::Novalnet, + connectors::Inespay, + connectors::Jpmorgan, connectors::Mollie, + connectors::Multisafepay, + connectors::Nexinets, connectors::Nexixpay, - connectors::Fiuu, - connectors::Globepay, - connectors::Worldline, + connectors::Paybox, + connectors::Nomupay, + connectors::Novalnet, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, + connectors::Stax, + connectors::Square, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Volt, - connectors::Deutschebank + connectors::Worldline, + connectors::Worldpay, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); macro_rules! default_imp_for_session_update { @@ -167,29 +235,131 @@ macro_rules! default_imp_for_session_update { } default_imp_for_session_update!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, + connectors::Forte, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Mollie, + connectors::Multisafepay, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, + connectors::UnifiedAuthenticationService, connectors::Fiuu, connectors::Globepay, + connectors::Gocardless, connectors::Worldline, + connectors::Worldpay, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, connectors::Powertranz, + connectors::Prophetpay, connectors::Thunes, connectors::Tsys, connectors::Deutschebank, - connectors::Volt + connectors::Volt, + connectors::CtpMastercard +); + +macro_rules! default_imp_for_post_session_tokens { + ($($path:ident::$connector:ident),*) => { + $( impl PaymentPostSessionTokens for $path::$connector {} + impl + ConnectorIntegration< + PostSessionTokens, + PaymentsPostSessionTokensData, + PaymentsResponseData, + > for $path::$connector + {} + )* + }; +} + +default_imp_for_post_session_tokens!( + connectors::Airwallex, + connectors::Amazonpay, + connectors::Bambora, + connectors::Bamboraapac, + connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, + connectors::Billwerk, + connectors::Cashtocode, + connectors::Coinbase, + connectors::Cryptopay, + connectors::Datatrans, + connectors::Digitalvirgo, + connectors::Dlocal, + connectors::Elavon, + connectors::Square, + connectors::Fiserv, + connectors::Fiservemea, + connectors::Forte, + connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, + connectors::Stax, + connectors::Taxjar, + connectors::Mollie, + connectors::Multisafepay, + connectors::Nomupay, + connectors::Novalnet, + connectors::Nexinets, + connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, + connectors::Fiuu, + connectors::Globepay, + connectors::Gocardless, + connectors::Worldline, + connectors::Worldpay, + connectors::Xendit, + connectors::Powertranz, + connectors::Prophetpay, + connectors::Thunes, + connectors::Tsys, + connectors::UnifiedAuthenticationService, + connectors::Deutschebank, + connectors::Volt, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); use crate::connectors; @@ -209,25 +379,48 @@ macro_rules! default_imp_for_complete_authorize { } default_imp_for_complete_authorize!( + connectors::Amazonpay, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Multisafepay, + connectors::Nomupay, connectors::Novalnet, - connectors::Nexixpay, + connectors::Nexinets, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); macro_rules! default_imp_for_incremental_authorization { @@ -246,29 +439,60 @@ macro_rules! default_imp_for_incremental_authorization { } default_imp_for_incremental_authorization!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); macro_rules! default_imp_for_create_customer { @@ -287,28 +511,58 @@ macro_rules! default_imp_for_create_customer { } default_imp_for_create_customer!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, connectors::Mollie, + connectors::Multisafepay, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); macro_rules! default_imp_for_connector_redirect_response { @@ -329,26 +583,51 @@ macro_rules! default_imp_for_connector_redirect_response { } default_imp_for_connector_redirect_response!( + connectors::Amazonpay, + connectors::Billwerk, connectors::Bitpay, + connectors::Boku, + connectors::Bamboraapac, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Deutschebank, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Multisafepay, + connectors::Nexinets, connectors::Nexixpay, + connectors::Nomupay, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Volt, + connectors::Xendit, + connectors::Zsl, + connectors::CtpMastercard ); macro_rules! default_imp_for_pre_processing_steps{ @@ -367,29 +646,56 @@ macro_rules! default_imp_for_pre_processing_steps{ } default_imp_for_pre_processing_steps!( + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, - connectors::Nexixpay, + connectors::Nexinets, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); macro_rules! default_imp_for_post_processing_steps{ @@ -408,29 +714,60 @@ macro_rules! default_imp_for_post_processing_steps{ } default_imp_for_post_processing_steps!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); macro_rules! default_imp_for_approve { @@ -449,29 +786,60 @@ macro_rules! default_imp_for_approve { } default_imp_for_approve!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); macro_rules! default_imp_for_reject { @@ -490,29 +858,60 @@ macro_rules! default_imp_for_reject { } default_imp_for_reject!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); macro_rules! default_imp_for_webhook_source_verification { @@ -531,29 +930,60 @@ macro_rules! default_imp_for_webhook_source_verification { } default_imp_for_webhook_source_verification!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); macro_rules! default_imp_for_accept_dispute { @@ -573,29 +1003,60 @@ macro_rules! default_imp_for_accept_dispute { } default_imp_for_accept_dispute!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); macro_rules! default_imp_for_submit_evidence { @@ -614,29 +1075,60 @@ macro_rules! default_imp_for_submit_evidence { } default_imp_for_submit_evidence!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); macro_rules! default_imp_for_defend_dispute { @@ -655,29 +1147,60 @@ macro_rules! default_imp_for_defend_dispute { } default_imp_for_defend_dispute!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, + connectors::Inespay, + connectors::Jpmorgan, connectors::Helcim, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); macro_rules! default_imp_for_file_upload { @@ -705,29 +1228,124 @@ macro_rules! default_imp_for_file_upload { } default_imp_for_file_upload!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, + connectors::Worldline, + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard +); + +macro_rules! default_imp_for_payouts { + ($($path:ident::$connector:ident),*) => { + $( + impl api::Payouts for $path::$connector {} + )* + }; +} + +default_imp_for_payouts!( + connectors::Airwallex, + connectors::Amazonpay, + connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, + connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, + connectors::Cashtocode, + connectors::Cryptopay, + connectors::Datatrans, + connectors::Coinbase, + connectors::Deutschebank, + connectors::Digitalvirgo, + connectors::Dlocal, + connectors::Elavon, + connectors::Fiserv, + connectors::Fiservemea, + connectors::Fiuu, + connectors::Forte, + connectors::Globepay, + connectors::Gocardless, + connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Mollie, + connectors::Multisafepay, + connectors::Nexinets, + connectors::Nexixpay, + connectors::Paybox, + connectors::Nomupay, + connectors::Novalnet, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, + connectors::Powertranz, + connectors::Prophetpay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, + connectors::Square, + connectors::Stax, + connectors::Taxjar, + connectors::Tsys, + connectors::UnifiedAuthenticationService, + connectors::Volt, connectors::Worldline, - connectors::Volt + connectors::Worldpay, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); #[cfg(feature = "payouts")] @@ -748,29 +1366,60 @@ macro_rules! default_imp_for_payouts_create { #[cfg(feature = "payouts")] default_imp_for_payouts_create!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); #[cfg(feature = "payouts")] @@ -791,29 +1440,60 @@ macro_rules! default_imp_for_payouts_retrieve { #[cfg(feature = "payouts")] default_imp_for_payouts_retrieve!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); #[cfg(feature = "payouts")] @@ -834,29 +1514,60 @@ macro_rules! default_imp_for_payouts_eligibility { #[cfg(feature = "payouts")] default_imp_for_payouts_eligibility!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); #[cfg(feature = "payouts")] @@ -877,29 +1588,60 @@ macro_rules! default_imp_for_payouts_fulfill { #[cfg(feature = "payouts")] default_imp_for_payouts_fulfill!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); #[cfg(feature = "payouts")] @@ -920,29 +1662,60 @@ macro_rules! default_imp_for_payouts_cancel { #[cfg(feature = "payouts")] default_imp_for_payouts_cancel!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); #[cfg(feature = "payouts")] @@ -963,29 +1736,60 @@ macro_rules! default_imp_for_payouts_quote { #[cfg(feature = "payouts")] default_imp_for_payouts_quote!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); #[cfg(feature = "payouts")] @@ -1006,29 +1810,60 @@ macro_rules! default_imp_for_payouts_recipient { #[cfg(feature = "payouts")] default_imp_for_payouts_recipient!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); #[cfg(feature = "payouts")] @@ -1049,29 +1884,60 @@ macro_rules! default_imp_for_payouts_recipient_account { #[cfg(feature = "payouts")] default_imp_for_payouts_recipient_account!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); #[cfg(feature = "frm")] @@ -1092,29 +1958,60 @@ macro_rules! default_imp_for_frm_sale { #[cfg(feature = "frm")] default_imp_for_frm_sale!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); #[cfg(feature = "frm")] @@ -1135,29 +2032,60 @@ macro_rules! default_imp_for_frm_checkout { #[cfg(feature = "frm")] default_imp_for_frm_checkout!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); #[cfg(feature = "frm")] @@ -1178,29 +2106,60 @@ macro_rules! default_imp_for_frm_transaction { #[cfg(feature = "frm")] default_imp_for_frm_transaction!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); #[cfg(feature = "frm")] @@ -1221,29 +2180,60 @@ macro_rules! default_imp_for_frm_fulfillment { #[cfg(feature = "frm")] default_imp_for_frm_fulfillment!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); #[cfg(feature = "frm")] @@ -1264,29 +2254,60 @@ macro_rules! default_imp_for_frm_record_return { #[cfg(feature = "frm")] default_imp_for_frm_record_return!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard ); macro_rules! default_imp_for_revoking_mandates { @@ -1304,27 +2325,199 @@ macro_rules! default_imp_for_revoking_mandates { } default_imp_for_revoking_mandates!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl, + connectors::CtpMastercard +); + +macro_rules! default_imp_for_uas_pre_authentication { + ($($path:ident::$connector:ident),*) => { + $( impl UnifiedAuthenticationService for $path::$connector {} + impl UasPreAuthentication for $path::$connector {} + impl + ConnectorIntegration< + PreAuthenticate, + UasPreAuthenticationRequestData, + UasAuthenticationResponseData + > for $path::$connector + {} + )* + }; +} + +default_imp_for_uas_pre_authentication!( + connectors::Airwallex, + connectors::Amazonpay, + connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, + connectors::Bluesnap, + connectors::Bitpay, + connectors::Boku, + connectors::Cashtocode, + connectors::Coinbase, + connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Datatrans, + connectors::Deutschebank, + connectors::Digitalvirgo, + connectors::Dlocal, + connectors::Elavon, + connectors::Fiserv, + connectors::Fiservemea, + connectors::Fiuu, + connectors::Forte, + connectors::Globepay, + connectors::Gocardless, + connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, + connectors::Novalnet, + connectors::Nexinets, + connectors::Nexixpay, + connectors::Payeezy, + connectors::Payu, + connectors::Powertranz, + connectors::Prophetpay, + connectors::Mollie, + connectors::Multisafepay, + connectors::Paybox, + connectors::Placetopay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, + connectors::Stax, + connectors::Square, + connectors::Taxjar, + connectors::Thunes, + connectors::Tsys, + connectors::Worldline, + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl +); + +macro_rules! default_imp_for_uas_post_authentication { + ($($path:ident::$connector:ident),*) => { + $( impl UasPostAuthentication for $path::$connector {} + impl + ConnectorIntegration< + PostAuthenticate, + UasPostAuthenticationRequestData, + UasAuthenticationResponseData + > for $path::$connector + {} + )* + }; +} + +default_imp_for_uas_post_authentication!( + connectors::Airwallex, + connectors::Amazonpay, + connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, + connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, + connectors::Cashtocode, + connectors::Coinbase, + connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Datatrans, + connectors::Deutschebank, + connectors::Digitalvirgo, + connectors::Dlocal, + connectors::Elavon, + connectors::Fiserv, + connectors::Fiservemea, + connectors::Fiuu, + connectors::Forte, + connectors::Globepay, + connectors::Gocardless, + connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, + connectors::Novalnet, + connectors::Nexinets, + connectors::Nexixpay, + connectors::Payeezy, + connectors::Payu, + connectors::Powertranz, + connectors::Prophetpay, + connectors::Mollie, + connectors::Multisafepay, + connectors::Paybox, + connectors::Placetopay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, + connectors::Stax, + connectors::Square, + connectors::Taxjar, + connectors::Thunes, + connectors::Tsys, + connectors::Worldline, + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl ); diff --git a/crates/hyperswitch_connectors/src/default_implementations_v2.rs b/crates/hyperswitch_connectors/src/default_implementations_v2.rs index d4e1836ac5f2..4161b36f906e 100644 --- a/crates/hyperswitch_connectors/src/default_implementations_v2.rs +++ b/crates/hyperswitch_connectors/src/default_implementations_v2.rs @@ -14,7 +14,8 @@ use hyperswitch_domain_models::{ payments::{ Approve, Authorize, AuthorizeSessionToken, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, IncrementalAuthorization, PSync, PaymentMethodToken, - PostProcessing, PreProcessing, Reject, SdkSessionUpdate, Session, SetupMandate, Void, + PostProcessing, PostSessionTokens, PreProcessing, Reject, SdkSessionUpdate, Session, + SetupMandate, Void, }, refunds::{Execute, RSync}, webhooks::VerifyWebhookSource, @@ -26,10 +27,10 @@ use hyperswitch_domain_models::{ MandateRevokeRequestData, PaymentMethodTokenizationData, PaymentsApproveData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, - PaymentsPreProcessingData, PaymentsRejectData, PaymentsSessionData, PaymentsSyncData, - PaymentsTaxCalculationData, RefundsData, RetrieveFileRequestData, - SdkPaymentsSessionUpdateData, SetupMandateRequestData, SubmitEvidenceRequestData, - UploadFileRequestData, VerifyWebhookSourceRequestData, + PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsRejectData, + PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, RefundsData, + RetrieveFileRequestData, SdkPaymentsSessionUpdateData, SetupMandateRequestData, + SubmitEvidenceRequestData, UploadFileRequestData, VerifyWebhookSourceRequestData, }, router_response_types::{ AcceptDisputeResponse, DefendDisputeResponse, MandateRevokeResponseData, @@ -74,8 +75,8 @@ use hyperswitch_interfaces::{ payments_v2::{ ConnectorCustomerV2, MandateSetupV2, PaymentApproveV2, PaymentAuthorizeSessionTokenV2, PaymentAuthorizeV2, PaymentCaptureV2, PaymentIncrementalAuthorizationV2, - PaymentRejectV2, PaymentSessionUpdateV2, PaymentSessionV2, PaymentSyncV2, - PaymentTokenV2, PaymentV2, PaymentVoidV2, PaymentsCompleteAuthorizeV2, + PaymentPostSessionTokensV2, PaymentRejectV2, PaymentSessionUpdateV2, PaymentSessionV2, + PaymentSyncV2, PaymentTokenV2, PaymentV2, PaymentVoidV2, PaymentsCompleteAuthorizeV2, PaymentsPostProcessingV2, PaymentsPreProcessingV2, TaxCalculationV2, }, refunds_v2::{RefundExecuteV2, RefundSyncV2, RefundV2}, @@ -107,6 +108,7 @@ macro_rules! default_imp_for_new_connector_integration_payment { impl PaymentsPostProcessingV2 for $path::$connector{} impl TaxCalculationV2 for $path::$connector{} impl PaymentSessionUpdateV2 for $path::$connector{} + impl PaymentPostSessionTokensV2 for $path::$connector{} impl ConnectorIntegrationV2 for $path::$connector{} @@ -191,34 +193,72 @@ macro_rules! default_imp_for_new_connector_integration_payment { SdkPaymentsSessionUpdateData, PaymentsResponseData, > for $path::$connector{} + impl + ConnectorIntegrationV2< + PostSessionTokens, + PaymentFlowData, + PaymentsPostSessionTokensData, + PaymentsResponseData, + > for $path::$connector{} )* }; } default_imp_for_new_connector_integration_payment!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Volt, + connectors::Worldpay, + connectors::Xendit, + connectors::Zen, + connectors::Zsl ); macro_rules! default_imp_for_new_connector_integration_refund { @@ -238,29 +278,60 @@ macro_rules! default_imp_for_new_connector_integration_refund { } default_imp_for_new_connector_integration_refund!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Volt, + connectors::Worldpay, + connectors::Xendit, + connectors::Zen, + connectors::Zsl ); macro_rules! default_imp_for_new_connector_integration_connector_access_token { @@ -275,29 +346,60 @@ macro_rules! default_imp_for_new_connector_integration_connector_access_token { } default_imp_for_new_connector_integration_connector_access_token!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Volt, + connectors::Worldpay, + connectors::Xendit, + connectors::Zen, + connectors::Zsl ); macro_rules! default_imp_for_new_connector_integration_accept_dispute { @@ -318,29 +420,60 @@ macro_rules! default_imp_for_new_connector_integration_accept_dispute { } default_imp_for_new_connector_integration_accept_dispute!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Volt, + connectors::Worldpay, + connectors::Xendit, + connectors::Zen, + connectors::Zsl ); macro_rules! default_imp_for_new_connector_integration_submit_evidence { @@ -360,29 +493,60 @@ macro_rules! default_imp_for_new_connector_integration_submit_evidence { } default_imp_for_new_connector_integration_submit_evidence!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Volt, + connectors::Worldpay, + connectors::Xendit, + connectors::Zen, + connectors::Zsl ); macro_rules! default_imp_for_new_connector_integration_defend_dispute { @@ -402,29 +566,60 @@ macro_rules! default_imp_for_new_connector_integration_defend_dispute { } default_imp_for_new_connector_integration_defend_dispute!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Volt, + connectors::Worldpay, + connectors::Xendit, + connectors::Zen, + connectors::Zsl ); macro_rules! default_imp_for_new_connector_integration_file_upload { @@ -454,29 +649,60 @@ macro_rules! default_imp_for_new_connector_integration_file_upload { } default_imp_for_new_connector_integration_file_upload!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Volt, + connectors::Worldpay, + connectors::Xendit, + connectors::Zen, + connectors::Zsl ); #[cfg(feature = "payouts")] @@ -498,29 +724,60 @@ macro_rules! default_imp_for_new_connector_integration_payouts_create { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_create!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Volt, + connectors::Worldpay, + connectors::Xendit, + connectors::Zen, + connectors::Zsl ); #[cfg(feature = "payouts")] @@ -542,29 +799,60 @@ macro_rules! default_imp_for_new_connector_integration_payouts_eligibility { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_eligibility!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Volt, + connectors::Worldpay, + connectors::Xendit, + connectors::Zen, + connectors::Zsl ); #[cfg(feature = "payouts")] @@ -586,29 +874,60 @@ macro_rules! default_imp_for_new_connector_integration_payouts_fulfill { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_fulfill!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Volt, + connectors::Worldpay, + connectors::Xendit, + connectors::Zen, + connectors::Zsl ); #[cfg(feature = "payouts")] @@ -630,29 +949,60 @@ macro_rules! default_imp_for_new_connector_integration_payouts_cancel { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_cancel!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Volt, + connectors::Worldpay, + connectors::Xendit, + connectors::Zen, + connectors::Zsl ); #[cfg(feature = "payouts")] @@ -674,29 +1024,60 @@ macro_rules! default_imp_for_new_connector_integration_payouts_quote { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_quote!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Volt, + connectors::Worldpay, + connectors::Xendit, + connectors::Zen, + connectors::Zsl ); #[cfg(feature = "payouts")] @@ -718,29 +1099,60 @@ macro_rules! default_imp_for_new_connector_integration_payouts_recipient { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_recipient!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Volt, + connectors::Worldpay, + connectors::Xendit, + connectors::Zen, + connectors::Zsl ); #[cfg(feature = "payouts")] @@ -762,29 +1174,60 @@ macro_rules! default_imp_for_new_connector_integration_payouts_sync { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_sync!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Volt, + connectors::Worldpay, + connectors::Xendit, + connectors::Zen, + connectors::Zsl ); #[cfg(feature = "payouts")] @@ -806,29 +1249,60 @@ macro_rules! default_imp_for_new_connector_integration_payouts_recipient_account #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_recipient_account!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Volt, + connectors::Worldpay, + connectors::Xendit, + connectors::Zen, + connectors::Zsl ); macro_rules! default_imp_for_new_connector_integration_webhook_source_verification { @@ -848,29 +1322,60 @@ macro_rules! default_imp_for_new_connector_integration_webhook_source_verificati } default_imp_for_new_connector_integration_webhook_source_verification!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Volt, + connectors::Worldpay, + connectors::Xendit, + connectors::Zen, + connectors::Zsl ); #[cfg(feature = "frm")] @@ -892,29 +1397,60 @@ macro_rules! default_imp_for_new_connector_integration_frm_sale { #[cfg(feature = "frm")] default_imp_for_new_connector_integration_frm_sale!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Volt, + connectors::Worldpay, + connectors::Xendit, + connectors::Zen, + connectors::Zsl ); #[cfg(feature = "frm")] @@ -936,29 +1472,60 @@ macro_rules! default_imp_for_new_connector_integration_frm_checkout { #[cfg(feature = "frm")] default_imp_for_new_connector_integration_frm_checkout!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Volt, + connectors::Worldpay, + connectors::Xendit, + connectors::Zen, + connectors::Zsl ); #[cfg(feature = "frm")] @@ -980,29 +1547,60 @@ macro_rules! default_imp_for_new_connector_integration_frm_transaction { #[cfg(feature = "frm")] default_imp_for_new_connector_integration_frm_transaction!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Volt, + connectors::Worldpay, + connectors::Xendit, + connectors::Zen, + connectors::Zsl ); #[cfg(feature = "frm")] @@ -1024,29 +1622,60 @@ macro_rules! default_imp_for_new_connector_integration_frm_fulfillment { #[cfg(feature = "frm")] default_imp_for_new_connector_integration_frm_fulfillment!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Volt, + connectors::Worldpay, + connectors::Xendit, + connectors::Zen, + connectors::Zsl ); #[cfg(feature = "frm")] @@ -1068,29 +1697,60 @@ macro_rules! default_imp_for_new_connector_integration_frm_record_return { #[cfg(feature = "frm")] default_imp_for_new_connector_integration_frm_record_return!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Volt, + connectors::Worldpay, + connectors::Xendit, + connectors::Zen, + connectors::Zsl ); macro_rules! default_imp_for_new_connector_integration_revoking_mandates { @@ -1109,27 +1769,58 @@ macro_rules! default_imp_for_new_connector_integration_revoking_mandates { } default_imp_for_new_connector_integration_revoking_mandates!( + connectors::Airwallex, + connectors::Amazonpay, connectors::Bambora, + connectors::Bamboraapac, + connectors::Billwerk, connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, connectors::Cashtocode, connectors::Coinbase, connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Datatrans, connectors::Deutschebank, + connectors::Digitalvirgo, connectors::Dlocal, + connectors::Elavon, connectors::Fiserv, connectors::Fiservemea, connectors::Fiuu, + connectors::Forte, connectors::Globepay, + connectors::Gocardless, connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, connectors::Novalnet, + connectors::Nexinets, connectors::Nexixpay, + connectors::Paybox, + connectors::Payeezy, + connectors::Payu, + connectors::Placetopay, connectors::Powertranz, + connectors::Prophetpay, connectors::Mollie, + connectors::Multisafepay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, connectors::Stax, connectors::Square, connectors::Taxjar, connectors::Thunes, connectors::Tsys, + connectors::UnifiedAuthenticationService, connectors::Worldline, - connectors::Volt + connectors::Volt, + connectors::Worldpay, + connectors::Xendit, + connectors::Zen, + connectors::Zsl ); diff --git a/crates/hyperswitch_connectors/src/lib.rs b/crates/hyperswitch_connectors/src/lib.rs index aa0ff7e975ea..5873398d8e47 100644 --- a/crates/hyperswitch_connectors/src/lib.rs +++ b/crates/hyperswitch_connectors/src/lib.rs @@ -4,5 +4,6 @@ pub mod connectors; pub mod constants; pub mod default_implementations; pub mod default_implementations_v2; +pub mod metrics; pub mod types; pub mod utils; diff --git a/crates/hyperswitch_connectors/src/metrics.rs b/crates/hyperswitch_connectors/src/metrics.rs new file mode 100644 index 000000000000..b4e8a2dbd25a --- /dev/null +++ b/crates/hyperswitch_connectors/src/metrics.rs @@ -0,0 +1,7 @@ +//! Metrics interface + +use router_env::{counter_metric, global_meter}; + +global_meter!(GLOBAL_METER, "ROUTER_API"); + +counter_metric!(CONNECTOR_RESPONSE_DESERIALIZATION_FAILURE, GLOBAL_METER); diff --git a/crates/hyperswitch_connectors/src/types.rs b/crates/hyperswitch_connectors/src/types.rs index 05279b53ef89..05ef62d78282 100644 --- a/crates/hyperswitch_connectors/src/types.rs +++ b/crates/hyperswitch_connectors/src/types.rs @@ -1,25 +1,29 @@ use hyperswitch_domain_models::{ router_data::{AccessToken, RouterData}, - router_flow_types::{AccessTokenAuth, Capture, PSync, Void}, + router_flow_types::{AccessTokenAuth, Capture, PSync, PreProcessing, Session, Void}, router_request_types::{ - AccessTokenRequestData, PaymentsCancelData, PaymentsCaptureData, PaymentsSyncData, - RefundsData, + AccessTokenRequestData, PaymentsCancelData, PaymentsCaptureData, PaymentsPreProcessingData, + PaymentsSessionData, PaymentsSyncData, RefundsData, }, router_response_types::{PaymentsResponseData, RefundsResponseData}, }; -pub type PaymentsSyncResponseRouterData = +pub(crate) type PaymentsSyncResponseRouterData = ResponseRouterData; -pub type PaymentsCaptureResponseRouterData = +pub(crate) type PaymentsCaptureResponseRouterData = ResponseRouterData; pub(crate) type RefundsResponseRouterData = ResponseRouterData; pub(crate) type RefreshTokenRouterData = RouterData; -pub type PaymentsCancelResponseRouterData = +pub(crate) type PaymentsCancelResponseRouterData = ResponseRouterData; +pub(crate) type PaymentsPreprocessingResponseRouterData = + ResponseRouterData; +pub(crate) type PaymentsSessionResponseRouterData = + ResponseRouterData; // TODO: Remove `ResponseRouterData` from router crate after all the related type aliases are moved to this crate. pub struct ResponseRouterData { diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index f96d715f5be4..9061a758beab 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -1,6 +1,6 @@ use std::collections::{HashMap, HashSet}; -use api_models::payments::{self, Address, AddressDetails, OrderDetailsWithAmount, PhoneDetails}; +use api_models::payments; use base64::Engine; use common_enums::{ enums, @@ -16,20 +16,29 @@ use common_utils::{ }; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ - payment_method_data::{Card, PaymentMethodData}, - router_data::{PaymentMethodToken, RecurringMandatePaymentData}, + address::{Address, AddressDetails, PhoneDetails}, + payment_method_data::{self, Card, PaymentMethodData}, + router_data::{ + ApplePayPredecryptData, ErrorResponse, PaymentMethodToken, RecurringMandatePaymentData, + }, router_request_types::{ - AuthenticationData, BrowserInformation, CompleteAuthorizeData, + AuthenticationData, BrowserInformation, CompleteAuthorizeData, ConnectorCustomerData, PaymentMethodTokenizationData, PaymentsAuthorizeData, PaymentsCancelData, - PaymentsCaptureData, PaymentsSyncData, RefundsData, ResponseId, SetupMandateRequestData, + PaymentsCaptureData, PaymentsPreProcessingData, PaymentsSyncData, RefundsData, ResponseId, + SetupMandateRequestData, }, + types::OrderDetailsWithAmount, }; -use hyperswitch_interfaces::{api, errors}; +use hyperswitch_interfaces::{api, consts, errors, types::Response}; use image::Luma; use masking::{ExposeInterface, PeekInterface, Secret}; use once_cell::sync::Lazy; use regex::Regex; +use router_env::logger; use serde::Serializer; +use serde_json::Value; + +use crate::{constants::UNSUPPORTED_ERROR_MESSAGE, types::RefreshTokenRouterData}; type Error = error_stack::Report; @@ -44,6 +53,15 @@ pub(crate) fn construct_not_supported_error_report( .into() } +pub(crate) fn to_currency_base_unit_with_zero_decimal_check( + amount: i64, + currency: enums::Currency, +) -> Result> { + currency + .to_currency_base_unit_with_zero_decimal_check(amount) + .change_context(errors::ConnectorError::RequestEncodingFailed) +} + pub(crate) fn get_amount_as_string( currency_unit: &api::CurrencyUnit, amount: i64, @@ -65,6 +83,102 @@ pub(crate) fn to_currency_base_unit( .change_context(errors::ConnectorError::ParsingFailed) } +pub(crate) fn to_currency_lower_unit( + amount: String, + currency: enums::Currency, +) -> Result> { + currency + .to_currency_lower_unit(amount) + .change_context(errors::ConnectorError::ResponseHandlingFailed) +} + +pub trait ConnectorErrorTypeMapping { + fn get_connector_error_type( + &self, + _error_code: String, + _error_message: String, + ) -> ConnectorErrorType { + ConnectorErrorType::UnknownError + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ErrorCodeAndMessage { + pub error_code: String, + pub error_message: String, +} + +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug)] +//Priority of connector_error_type +pub enum ConnectorErrorType { + UserError = 2, + BusinessError = 3, + TechnicalError = 4, + UnknownError = 1, +} + +pub(crate) fn get_error_code_error_message_based_on_priority( + connector: impl ConnectorErrorTypeMapping, + error_list: Vec, +) -> Option { + let error_type_list = error_list + .iter() + .map(|error| { + connector + .get_connector_error_type(error.error_code.clone(), error.error_message.clone()) + }) + .collect::>(); + let mut error_zip_list = error_list + .iter() + .zip(error_type_list.iter()) + .collect::>(); + error_zip_list.sort_by_key(|&(_, error_type)| error_type); + error_zip_list + .first() + .map(|&(error_code_message, _)| error_code_message) + .cloned() +} + +#[derive(Clone, Debug, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GooglePayWalletData { + #[serde(rename = "type")] + pub pm_type: String, + pub description: String, + pub info: GooglePayPaymentMethodInfo, + pub tokenization_data: GpayTokenizationData, +} + +#[derive(Clone, Debug, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GooglePayPaymentMethodInfo { + pub card_network: String, + pub card_details: String, +} + +#[derive(Clone, Debug, serde::Serialize)] +pub struct GpayTokenizationData { + #[serde(rename = "type")] + pub token_type: String, + pub token: Secret, +} + +impl From for GooglePayWalletData { + fn from(data: payment_method_data::GooglePayWalletData) -> Self { + Self { + pm_type: data.pm_type, + description: data.description, + info: GooglePayPaymentMethodInfo { + card_network: data.info.card_network, + card_details: data.info.card_details, + }, + tokenization_data: GpayTokenizationData { + token_type: data.tokenization_data.token_type, + token: Secret::new(data.tokenization_data.token), + }, + } + } +} pub(crate) fn get_amount_as_f64( currency_unit: &api::CurrencyUnit, amount: i64, @@ -89,7 +203,7 @@ pub(crate) fn to_currency_base_unit_asf64( } pub(crate) fn to_connector_meta_from_secret( - connector_meta: Option>, + connector_meta: Option>, ) -> Result where T: serde::de::DeserializeOwned, @@ -100,9 +214,15 @@ where json.parse_value(std::any::type_name::()).switch() } +pub(crate) fn generate_random_bytes(length: usize) -> Vec { + // returns random bytes of length n + let mut rng = rand::thread_rng(); + (0..length).map(|_| rand::Rng::gen(&mut rng)).collect() +} + pub(crate) fn missing_field_err( message: &'static str, -) -> Box error_stack::Report + '_> { +) -> Box error_stack::Report + 'static> { Box::new(move || { errors::ConnectorError::MissingRequiredField { field_name: message, @@ -111,6 +231,36 @@ pub(crate) fn missing_field_err( }) } +pub(crate) fn handle_json_response_deserialization_failure( + res: Response, + connector: &'static str, +) -> CustomResult { + crate::metrics::CONNECTOR_RESPONSE_DESERIALIZATION_FAILURE + .add(1, router_env::metric_attributes!(("connector", connector))); + + let response_data = String::from_utf8(res.response.to_vec()) + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + // check for whether the response is in json format + match serde_json::from_str::(&response_data) { + // in case of unexpected response but in json format + Ok(_) => Err(errors::ConnectorError::ResponseDeserializationFailed)?, + // in case of unexpected response but in html or string format + Err(error_msg) => { + logger::error!(deserialization_error=?error_msg); + logger::error!("UNEXPECTED RESPONSE FROM CONNECTOR: {}", response_data); + Ok(ErrorResponse { + status_code: res.status_code, + code: consts::NO_ERROR_CODE.to_string(), + message: UNSUPPORTED_ERROR_MESSAGE.to_string(), + reason: Some(response_data), + attempt_status: None, + connector_transaction_id: None, + }) + } + } +} + pub(crate) fn construct_not_implemented_error_report( capture_method: enums::CaptureMethod, connector_name: &str, @@ -135,7 +285,7 @@ pub(crate) fn get_unimplemented_payment_method_error_message(connector: &str) -> format!("{} through {}", SELECTED_PAYMENT_METHOD, connector) } -pub(crate) fn to_connector_meta(connector_meta: Option) -> Result +pub(crate) fn to_connector_meta(connector_meta: Option) -> Result where T: serde::de::DeserializeOwned, { @@ -192,13 +342,22 @@ pub(crate) fn is_payment_failure(status: AttemptStatus) -> bool { } } +pub fn is_refund_failure(status: enums::RefundStatus) -> bool { + match status { + common_enums::RefundStatus::Failure | common_enums::RefundStatus::TransactionFailure => { + true + } + common_enums::RefundStatus::ManualReview + | common_enums::RefundStatus::Pending + | common_enums::RefundStatus::Success => false, + } +} // TODO: Make all traits as `pub(crate) trait` once all connectors are moved. pub trait RouterData { fn get_billing(&self) -> Result<&Address, Error>; fn get_billing_country(&self) -> Result; fn get_billing_phone(&self) -> Result<&PhoneDetails, Error>; fn get_description(&self) -> Result; - fn get_return_url(&self) -> Result; fn get_billing_address(&self) -> Result<&AddressDetails, Error>; fn get_shipping_address(&self) -> Result<&AddressDetails, Error>; fn get_shipping_address_with_phone_number(&self) -> Result<&Address, Error>; @@ -239,6 +398,7 @@ pub trait RouterData { fn get_optional_shipping_state(&self) -> Option>; fn get_optional_shipping_first_name(&self) -> Option>; fn get_optional_shipping_last_name(&self) -> Option>; + fn get_optional_shipping_full_name(&self) -> Option>; fn get_optional_shipping_phone_number(&self) -> Option>; fn get_optional_shipping_email(&self) -> Option; @@ -308,6 +468,12 @@ impl RouterData }) } + fn get_optional_shipping_full_name(&self) -> Option> { + self.get_optional_shipping() + .and_then(|shipping_details| shipping_details.address.as_ref()) + .and_then(|shipping_address| shipping_address.get_optional_full_name()) + } + fn get_optional_shipping_line1(&self) -> Option> { self.address.get_shipping().and_then(|shipping_address| { shipping_address @@ -380,11 +546,6 @@ impl RouterData .clone() .ok_or_else(missing_field_err("description")) } - fn get_return_url(&self) -> Result { - self.return_url - .clone() - .ok_or_else(missing_field_err("return_url")) - } fn get_billing_address(&self) -> Result<&AddressDetails, Error> { self.address .get_payment_method_billing() @@ -721,6 +882,43 @@ impl RouterData } } +pub trait AccessTokenRequestInfo { + fn get_request_id(&self) -> Result, Error>; +} + +impl AccessTokenRequestInfo for RefreshTokenRouterData { + fn get_request_id(&self) -> Result, Error> { + self.request + .id + .clone() + .ok_or_else(missing_field_err("request.id")) + } +} +pub trait ApplePayDecrypt { + fn get_expiry_month(&self) -> Result, Error>; + fn get_four_digit_expiry_year(&self) -> Result, Error>; +} + +impl ApplePayDecrypt for Box { + fn get_four_digit_expiry_year(&self) -> Result, Error> { + Ok(Secret::new(format!( + "20{}", + self.application_expiration_date + .get(0..2) + .ok_or(errors::ConnectorError::RequestEncodingFailed)? + ))) + } + + fn get_expiry_month(&self) -> Result, Error> { + Ok(Secret::new( + self.application_expiration_date + .get(2..4) + .ok_or(errors::ConnectorError::RequestEncodingFailed)? + .to_owned(), + )) + } +} + #[derive(Debug, Copy, Clone, strum::Display, Eq, Hash, PartialEq)] pub enum CardIssuer { AmericanExpress, @@ -744,8 +942,10 @@ pub trait CardData { fn get_expiry_date_as_mmyyyy(&self, delimiter: &str) -> Secret; fn get_expiry_year_4_digit(&self) -> Secret; fn get_expiry_date_as_yymm(&self) -> Result, errors::ConnectorError>; + fn get_expiry_date_as_mmyy(&self) -> Result, errors::ConnectorError>; fn get_expiry_month_as_i8(&self) -> Result, Error>; fn get_expiry_year_as_i32(&self) -> Result, Error>; + fn get_expiry_year_as_4_digit_i32(&self) -> Result, Error>; } impl CardData for Card { @@ -803,6 +1003,11 @@ impl CardData for Card { let month = self.card_exp_month.clone().expose(); Ok(Secret::new(format!("{year}{month}"))) } + fn get_expiry_date_as_mmyy(&self) -> Result, errors::ConnectorError> { + let year = self.get_card_expiry_year_2_digit()?.expose(); + let month = self.card_exp_month.clone().expose(); + Ok(Secret::new(format!("{month}{year}"))) + } fn get_expiry_month_as_i8(&self) -> Result, Error> { self.card_exp_month .peek() @@ -819,6 +1024,14 @@ impl CardData for Card { .change_context(errors::ConnectorError::ResponseDeserializationFailed) .map(Secret::new) } + fn get_expiry_year_as_4_digit_i32(&self) -> Result, Error> { + self.get_expiry_year_4_digit() + .peek() + .clone() + .parse::() + .change_context(errors::ConnectorError::ResponseDeserializationFailed) + .map(Secret::new) + } } #[track_caller] @@ -1026,6 +1239,15 @@ impl PhoneDetailsData for PhoneDetails { } } +pub trait CustomerData { + fn get_email(&self) -> Result; +} + +impl CustomerData for ConnectorCustomerData { + fn get_email(&self) -> Result { + self.email.clone().ok_or_else(missing_field_err("email")) + } +} pub trait PaymentsAuthorizeRequestData { fn get_optional_language_from_browser_info(&self) -> Option; fn is_auto_capture(&self) -> Result; @@ -1033,7 +1255,6 @@ pub trait PaymentsAuthorizeRequestData { fn get_browser_info(&self) -> Result; fn get_order_details(&self) -> Result, Error>; fn get_card(&self) -> Result; - fn get_return_url(&self) -> Result; fn connector_mandate_id(&self) -> Option; fn is_mandate_payment(&self) -> bool; fn is_customer_initiated_mandate_payment(&self) -> bool; @@ -1051,16 +1272,25 @@ pub trait PaymentsAuthorizeRequestData { fn get_total_surcharge_amount(&self) -> Option; fn get_metadata_as_object(&self) -> Option; fn get_authentication_data(&self) -> Result; + fn get_customer_name(&self) -> Result, Error>; + fn get_connector_mandate_request_reference_id(&self) -> Result; + fn get_card_holder_name_from_additional_payment_method_data( + &self, + ) -> Result, Error>; + fn is_cit_mandate_payment(&self) -> bool; } impl PaymentsAuthorizeRequestData for PaymentsAuthorizeData { fn is_auto_capture(&self) -> Result { match self.capture_method { - Some(enums::CaptureMethod::Automatic) | None => Ok(true), + Some(enums::CaptureMethod::Automatic) + | Some(enums::CaptureMethod::SequentialAutomatic) + | None => Ok(true), Some(enums::CaptureMethod::Manual) => Ok(false), Some(_) => Err(errors::ConnectorError::CaptureMethodNotSupported.into()), } } + fn get_email(&self) -> Result { self.email.clone().ok_or_else(missing_field_err("email")) } @@ -1087,11 +1317,6 @@ impl PaymentsAuthorizeRequestData for PaymentsAuthorizeData { _ => Err(missing_field_err("card")()), } } - fn get_return_url(&self) -> Result { - self.router_return_url - .clone() - .ok_or_else(missing_field_err("return_url")) - } fn get_complete_authorize_url(&self) -> Result { self.complete_authorize_url @@ -1104,7 +1329,7 @@ impl PaymentsAuthorizeRequestData for PaymentsAuthorizeData { .as_ref() .and_then(|mandate_ids| match &mandate_ids.mandate_reference_id { Some(payments::MandateReferenceId::ConnectorMandateId(connector_mandate_ids)) => { - connector_mandate_ids.connector_mandate_id.clone() + connector_mandate_ids.get_connector_mandate_id() } Some(payments::MandateReferenceId::NetworkMandateId(_)) | None @@ -1191,12 +1416,12 @@ impl PaymentsAuthorizeRequestData for PaymentsAuthorizeData { fn get_metadata_as_object(&self) -> Option { self.metadata.clone().and_then(|meta_data| match meta_data { - serde_json::Value::Null - | serde_json::Value::Bool(_) - | serde_json::Value::Number(_) - | serde_json::Value::String(_) - | serde_json::Value::Array(_) => None, - serde_json::Value::Object(_) => Some(meta_data.into()), + Value::Null + | Value::Bool(_) + | Value::Number(_) + | Value::String(_) + | Value::Array(_) => None, + Value::Object(_) => Some(meta_data.into()), }) } @@ -1205,6 +1430,49 @@ impl PaymentsAuthorizeRequestData for PaymentsAuthorizeData { .clone() .ok_or_else(missing_field_err("authentication_data")) } + + fn get_customer_name(&self) -> Result, Error> { + self.customer_name + .clone() + .ok_or_else(missing_field_err("customer_name")) + } + + fn get_card_holder_name_from_additional_payment_method_data( + &self, + ) -> Result, Error> { + match &self.additional_payment_method_data { + Some(payments::AdditionalPaymentData::Card(card_data)) => Ok(card_data + .card_holder_name + .clone() + .ok_or_else(|| errors::ConnectorError::MissingRequiredField { + field_name: "card_holder_name", + })?), + _ => Err(errors::ConnectorError::MissingRequiredFields { + field_names: vec!["card_holder_name"], + } + .into()), + } + } + /// Attempts to retrieve the connector mandate reference ID as a `Result`. + fn get_connector_mandate_request_reference_id(&self) -> Result { + self.mandate_id + .as_ref() + .and_then(|mandate_ids| match &mandate_ids.mandate_reference_id { + Some(payments::MandateReferenceId::ConnectorMandateId(connector_mandate_ids)) => { + connector_mandate_ids.get_connector_mandate_request_reference_id() + } + Some(payments::MandateReferenceId::NetworkMandateId(_)) + | None + | Some(payments::MandateReferenceId::NetworkTokenWithNTI(_)) => None, + }) + .ok_or_else(missing_field_err("connector_mandate_request_reference_id")) + } + fn is_cit_mandate_payment(&self) -> bool { + (self.customer_acceptance.is_some() || self.setup_mandate_details.is_some()) + && self.setup_future_usage.map_or(false, |setup_future_usage| { + setup_future_usage == FutureUsage::OffSession + }) + } } pub trait PaymentsCaptureRequestData { @@ -1237,7 +1505,9 @@ pub trait PaymentsSyncRequestData { impl PaymentsSyncRequestData for PaymentsSyncData { fn is_auto_capture(&self) -> Result { match self.capture_method { - Some(enums::CaptureMethod::Automatic) | None => Ok(true), + Some(enums::CaptureMethod::Automatic) + | Some(enums::CaptureMethod::SequentialAutomatic) + | None => Ok(true), Some(enums::CaptureMethod::Manual) => Ok(false), Some(_) => Err(errors::ConnectorError::CaptureMethodNotSupported.into()), } @@ -1323,6 +1593,7 @@ impl RefundsRequestData for RefundsData { pub trait PaymentsSetupMandateRequestData { fn get_browser_info(&self) -> Result; fn get_email(&self) -> Result; + fn get_router_return_url(&self) -> Result; fn is_card(&self) -> bool; } @@ -1335,6 +1606,11 @@ impl PaymentsSetupMandateRequestData for SetupMandateRequestData { fn get_email(&self) -> Result { self.email.clone().ok_or_else(missing_field_err("email")) } + fn get_router_return_url(&self) -> Result { + self.router_return_url + .clone() + .ok_or_else(missing_field_err("router_return_url")) + } fn is_card(&self) -> bool { matches!(self.payment_method_data, PaymentMethodData::Card(_)) } @@ -1358,12 +1634,16 @@ pub trait PaymentsCompleteAuthorizeRequestData { fn get_redirect_response_payload(&self) -> Result; fn get_complete_authorize_url(&self) -> Result; fn is_mandate_payment(&self) -> bool; + fn get_connector_mandate_request_reference_id(&self) -> Result; + fn is_cit_mandate_payment(&self) -> bool; } impl PaymentsCompleteAuthorizeRequestData for CompleteAuthorizeData { fn is_auto_capture(&self) -> Result { match self.capture_method { - Some(enums::CaptureMethod::Automatic) | None => Ok(true), + Some(enums::CaptureMethod::Automatic) + | Some(enums::CaptureMethod::SequentialAutomatic) + | None => Ok(true), Some(enums::CaptureMethod::Manual) => Ok(false), Some(_) => Err(errors::ConnectorError::CaptureMethodNotSupported.into()), } @@ -1398,6 +1678,62 @@ impl PaymentsCompleteAuthorizeRequestData for CompleteAuthorizeData { .and_then(|mandate_ids| mandate_ids.mandate_reference_id.as_ref()) .is_some() } + /// Attempts to retrieve the connector mandate reference ID as a `Result`. + fn get_connector_mandate_request_reference_id(&self) -> Result { + self.mandate_id + .as_ref() + .and_then(|mandate_ids| match &mandate_ids.mandate_reference_id { + Some(payments::MandateReferenceId::ConnectorMandateId(connector_mandate_ids)) => { + connector_mandate_ids.get_connector_mandate_request_reference_id() + } + Some(payments::MandateReferenceId::NetworkMandateId(_)) + | None + | Some(payments::MandateReferenceId::NetworkTokenWithNTI(_)) => None, + }) + .ok_or_else(missing_field_err("connector_mandate_request_reference_id")) + } + fn is_cit_mandate_payment(&self) -> bool { + (self.customer_acceptance.is_some() || self.setup_mandate_details.is_some()) + && self.setup_future_usage.map_or(false, |setup_future_usage| { + setup_future_usage == FutureUsage::OffSession + }) + } +} +pub trait AddressData { + fn get_optional_full_name(&self) -> Option>; +} + +impl AddressData for Address { + fn get_optional_full_name(&self) -> Option> { + self.address + .as_ref() + .and_then(|billing_address| billing_address.get_optional_full_name()) + } +} +pub trait PaymentsPreProcessingRequestData { + fn get_amount(&self) -> Result; + fn get_currency(&self) -> Result; + fn is_auto_capture(&self) -> Result; +} + +impl PaymentsPreProcessingRequestData for PaymentsPreProcessingData { + fn is_auto_capture(&self) -> Result { + match self.capture_method { + Some(enums::CaptureMethod::Automatic) + | None + | Some(enums::CaptureMethod::SequentialAutomatic) => Ok(true), + Some(enums::CaptureMethod::Manual) => Ok(false), + Some(enums::CaptureMethod::ManualMultiple) | Some(enums::CaptureMethod::Scheduled) => { + Err(errors::ConnectorError::CaptureMethodNotSupported.into()) + } + } + } + fn get_amount(&self) -> Result { + self.amount.ok_or_else(missing_field_err("amount")) + } + fn get_currency(&self) -> Result { + self.currency.ok_or_else(missing_field_err("currency")) + } } pub trait BrowserInformationData { @@ -1411,6 +1747,9 @@ pub trait BrowserInformationData { fn get_java_enabled(&self) -> Result; fn get_java_script_enabled(&self) -> Result; fn get_ip_address(&self) -> Result, Error>; + fn get_os_type(&self) -> Result; + fn get_os_version(&self) -> Result; + fn get_device_model(&self) -> Result; } impl BrowserInformationData for BrowserInformation { @@ -1459,6 +1798,21 @@ impl BrowserInformationData for BrowserInformation { self.java_script_enabled .ok_or_else(missing_field_err("browser_info.java_script_enabled")) } + fn get_os_type(&self) -> Result { + self.os_type + .clone() + .ok_or_else(missing_field_err("browser_info.os_type")) + } + fn get_os_version(&self) -> Result { + self.os_version + .clone() + .ok_or_else(missing_field_err("browser_info.os_version")) + } + fn get_device_model(&self) -> Result { + self.device_model + .clone() + .ok_or_else(missing_field_err("browser_info.device_model")) + } } pub fn get_header_key_value<'a>( @@ -1493,7 +1847,7 @@ pub trait CryptoData { fn get_pay_currency(&self) -> Result; } -impl CryptoData for hyperswitch_domain_models::payment_method_data::CryptoData { +impl CryptoData for payment_method_data::CryptoData { fn get_pay_currency(&self) -> Result { self.pay_currency .clone() @@ -1733,6 +2087,7 @@ pub enum PaymentMethodDataType { MobilePayRedirect, PaypalRedirect, PaypalSdk, + Paze, SamsungPay, TwintRedirect, VippsRedirect, @@ -1743,6 +2098,7 @@ pub enum PaymentMethodDataType { SwishQr, KlarnaRedirect, KlarnaSdk, + KlarnaCheckout, AffirmRedirect, AfterpayClearpayRedirect, PayBrightRedirect, @@ -1812,6 +2168,8 @@ pub enum PaymentMethodDataType { VietQr, OpenBanking, NetworkToken, + NetworkTransactionIdAndCardDetails, + DirectCarrierBilling, } impl From for PaymentMethodDataType { @@ -1819,183 +2177,265 @@ impl From for PaymentMethodDataType { match pm_data { PaymentMethodData::Card(_) => Self::Card, PaymentMethodData::NetworkToken(_) => Self::NetworkToken, - PaymentMethodData::CardRedirect(card_redirect_data) => { - match card_redirect_data { - hyperswitch_domain_models::payment_method_data::CardRedirectData::Knet {} => Self::Knet, - hyperswitch_domain_models::payment_method_data::CardRedirectData::Benefit {} => Self::Benefit, - hyperswitch_domain_models::payment_method_data::CardRedirectData::MomoAtm {} => Self::MomoAtm, - hyperswitch_domain_models::payment_method_data::CardRedirectData::CardRedirect {} => Self::CardRedirect, - } + PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + Self::NetworkTransactionIdAndCardDetails } + PaymentMethodData::CardRedirect(card_redirect_data) => match card_redirect_data { + payment_method_data::CardRedirectData::Knet {} => Self::Knet, + payment_method_data::CardRedirectData::Benefit {} => Self::Benefit, + payment_method_data::CardRedirectData::MomoAtm {} => Self::MomoAtm, + payment_method_data::CardRedirectData::CardRedirect {} => Self::CardRedirect, + }, PaymentMethodData::Wallet(wallet_data) => match wallet_data { - hyperswitch_domain_models::payment_method_data::WalletData::AliPayQr(_) => Self::AliPayQr, - hyperswitch_domain_models::payment_method_data::WalletData::AliPayRedirect(_) => Self::AliPayRedirect, - hyperswitch_domain_models::payment_method_data::WalletData::AliPayHkRedirect(_) => Self::AliPayHkRedirect, - hyperswitch_domain_models::payment_method_data::WalletData::MomoRedirect(_) => Self::MomoRedirect, - hyperswitch_domain_models::payment_method_data::WalletData::KakaoPayRedirect(_) => Self::KakaoPayRedirect, - hyperswitch_domain_models::payment_method_data::WalletData::GoPayRedirect(_) => Self::GoPayRedirect, - hyperswitch_domain_models::payment_method_data::WalletData::GcashRedirect(_) => Self::GcashRedirect, - hyperswitch_domain_models::payment_method_data::WalletData::ApplePay(_) => Self::ApplePay, - hyperswitch_domain_models::payment_method_data::WalletData::ApplePayRedirect(_) => Self::ApplePayRedirect, - hyperswitch_domain_models::payment_method_data::WalletData::ApplePayThirdPartySdk(_) => { + payment_method_data::WalletData::AliPayQr(_) => Self::AliPayQr, + payment_method_data::WalletData::AliPayRedirect(_) => Self::AliPayRedirect, + payment_method_data::WalletData::AliPayHkRedirect(_) => Self::AliPayHkRedirect, + payment_method_data::WalletData::MomoRedirect(_) => Self::MomoRedirect, + payment_method_data::WalletData::KakaoPayRedirect(_) => Self::KakaoPayRedirect, + payment_method_data::WalletData::GoPayRedirect(_) => Self::GoPayRedirect, + payment_method_data::WalletData::GcashRedirect(_) => Self::GcashRedirect, + payment_method_data::WalletData::ApplePay(_) => Self::ApplePay, + payment_method_data::WalletData::ApplePayRedirect(_) => Self::ApplePayRedirect, + payment_method_data::WalletData::ApplePayThirdPartySdk(_) => { Self::ApplePayThirdPartySdk } - hyperswitch_domain_models::payment_method_data::WalletData::DanaRedirect {} => Self::DanaRedirect, - hyperswitch_domain_models::payment_method_data::WalletData::GooglePay(_) => Self::GooglePay, - hyperswitch_domain_models::payment_method_data::WalletData::GooglePayRedirect(_) => Self::GooglePayRedirect, - hyperswitch_domain_models::payment_method_data::WalletData::GooglePayThirdPartySdk(_) => { + payment_method_data::WalletData::DanaRedirect {} => Self::DanaRedirect, + payment_method_data::WalletData::GooglePay(_) => Self::GooglePay, + payment_method_data::WalletData::GooglePayRedirect(_) => Self::GooglePayRedirect, + payment_method_data::WalletData::GooglePayThirdPartySdk(_) => { Self::GooglePayThirdPartySdk } - hyperswitch_domain_models::payment_method_data::WalletData::MbWayRedirect(_) => Self::MbWayRedirect, - hyperswitch_domain_models::payment_method_data::WalletData::MobilePayRedirect(_) => Self::MobilePayRedirect, - hyperswitch_domain_models::payment_method_data::WalletData::PaypalRedirect(_) => Self::PaypalRedirect, - hyperswitch_domain_models::payment_method_data::WalletData::PaypalSdk(_) => Self::PaypalSdk, - hyperswitch_domain_models::payment_method_data::WalletData::SamsungPay(_) => Self::SamsungPay, - hyperswitch_domain_models::payment_method_data::WalletData::TwintRedirect {} => Self::TwintRedirect, - hyperswitch_domain_models::payment_method_data::WalletData::VippsRedirect {} => Self::VippsRedirect, - hyperswitch_domain_models::payment_method_data::WalletData::TouchNGoRedirect(_) => Self::TouchNGoRedirect, - hyperswitch_domain_models::payment_method_data::WalletData::WeChatPayRedirect(_) => Self::WeChatPayRedirect, - hyperswitch_domain_models::payment_method_data::WalletData::WeChatPayQr(_) => Self::WeChatPayQr, - hyperswitch_domain_models::payment_method_data::WalletData::CashappQr(_) => Self::CashappQr, - hyperswitch_domain_models::payment_method_data::WalletData::SwishQr(_) => Self::SwishQr, - hyperswitch_domain_models::payment_method_data::WalletData::Mifinity(_) => Self::Mifinity, + payment_method_data::WalletData::MbWayRedirect(_) => Self::MbWayRedirect, + payment_method_data::WalletData::MobilePayRedirect(_) => Self::MobilePayRedirect, + payment_method_data::WalletData::PaypalRedirect(_) => Self::PaypalRedirect, + payment_method_data::WalletData::PaypalSdk(_) => Self::PaypalSdk, + payment_method_data::WalletData::Paze(_) => Self::Paze, + payment_method_data::WalletData::SamsungPay(_) => Self::SamsungPay, + payment_method_data::WalletData::TwintRedirect {} => Self::TwintRedirect, + payment_method_data::WalletData::VippsRedirect {} => Self::VippsRedirect, + payment_method_data::WalletData::TouchNGoRedirect(_) => Self::TouchNGoRedirect, + payment_method_data::WalletData::WeChatPayRedirect(_) => Self::WeChatPayRedirect, + payment_method_data::WalletData::WeChatPayQr(_) => Self::WeChatPayQr, + payment_method_data::WalletData::CashappQr(_) => Self::CashappQr, + payment_method_data::WalletData::SwishQr(_) => Self::SwishQr, + payment_method_data::WalletData::Mifinity(_) => Self::Mifinity, }, PaymentMethodData::PayLater(pay_later_data) => match pay_later_data { - hyperswitch_domain_models::payment_method_data::PayLaterData::KlarnaRedirect { .. } => Self::KlarnaRedirect, - hyperswitch_domain_models::payment_method_data::PayLaterData::KlarnaSdk { .. } => Self::KlarnaSdk, - hyperswitch_domain_models::payment_method_data::PayLaterData::AffirmRedirect {} => Self::AffirmRedirect, - hyperswitch_domain_models::payment_method_data::PayLaterData::AfterpayClearpayRedirect { .. } => { + payment_method_data::PayLaterData::KlarnaRedirect { .. } => Self::KlarnaRedirect, + payment_method_data::PayLaterData::KlarnaSdk { .. } => Self::KlarnaSdk, + payment_method_data::PayLaterData::KlarnaCheckout {} => Self::KlarnaCheckout, + payment_method_data::PayLaterData::AffirmRedirect {} => Self::AffirmRedirect, + payment_method_data::PayLaterData::AfterpayClearpayRedirect { .. } => { Self::AfterpayClearpayRedirect } - hyperswitch_domain_models::payment_method_data::PayLaterData::PayBrightRedirect {} => Self::PayBrightRedirect, - hyperswitch_domain_models::payment_method_data::PayLaterData::WalleyRedirect {} => Self::WalleyRedirect, - hyperswitch_domain_models::payment_method_data::PayLaterData::AlmaRedirect {} => Self::AlmaRedirect, - hyperswitch_domain_models::payment_method_data::PayLaterData::AtomeRedirect {} => Self::AtomeRedirect, + payment_method_data::PayLaterData::PayBrightRedirect {} => Self::PayBrightRedirect, + payment_method_data::PayLaterData::WalleyRedirect {} => Self::WalleyRedirect, + payment_method_data::PayLaterData::AlmaRedirect {} => Self::AlmaRedirect, + payment_method_data::PayLaterData::AtomeRedirect {} => Self::AtomeRedirect, }, - PaymentMethodData::BankRedirect(bank_redirect_data) => { - match bank_redirect_data { - hyperswitch_domain_models::payment_method_data::BankRedirectData::BancontactCard { .. } => { - Self::BancontactCard - } - hyperswitch_domain_models::payment_method_data::BankRedirectData::Bizum {} => Self::Bizum, - hyperswitch_domain_models::payment_method_data::BankRedirectData::Blik { .. } => Self::Blik, - hyperswitch_domain_models::payment_method_data::BankRedirectData::Eps { .. } => Self::Eps, - hyperswitch_domain_models::payment_method_data::BankRedirectData::Giropay { .. } => Self::Giropay, - hyperswitch_domain_models::payment_method_data::BankRedirectData::Ideal { .. } => Self::Ideal, - hyperswitch_domain_models::payment_method_data::BankRedirectData::Interac { .. } => Self::Interac, - hyperswitch_domain_models::payment_method_data::BankRedirectData::OnlineBankingCzechRepublic { .. } => { - Self::OnlineBankingCzechRepublic - } - hyperswitch_domain_models::payment_method_data::BankRedirectData::OnlineBankingFinland { .. } => { - Self::OnlineBankingFinland - } - hyperswitch_domain_models::payment_method_data::BankRedirectData::OnlineBankingPoland { .. } => { - Self::OnlineBankingPoland - } - hyperswitch_domain_models::payment_method_data::BankRedirectData::OnlineBankingSlovakia { .. } => { - Self::OnlineBankingSlovakia - } - hyperswitch_domain_models::payment_method_data::BankRedirectData::OpenBankingUk { .. } => Self::OpenBankingUk, - hyperswitch_domain_models::payment_method_data::BankRedirectData::Przelewy24 { .. } => Self::Przelewy24, - hyperswitch_domain_models::payment_method_data::BankRedirectData::Sofort { .. } => Self::Sofort, - hyperswitch_domain_models::payment_method_data::BankRedirectData::Trustly { .. } => Self::Trustly, - hyperswitch_domain_models::payment_method_data::BankRedirectData::OnlineBankingFpx { .. } => { - Self::OnlineBankingFpx - } - hyperswitch_domain_models::payment_method_data::BankRedirectData::OnlineBankingThailand { .. } => { - Self::OnlineBankingThailand - } - hyperswitch_domain_models::payment_method_data::BankRedirectData::LocalBankRedirect { } => { - Self::LocalBankRedirect - } + PaymentMethodData::BankRedirect(bank_redirect_data) => match bank_redirect_data { + payment_method_data::BankRedirectData::BancontactCard { .. } => { + Self::BancontactCard } - } - PaymentMethodData::BankDebit(bank_debit_data) => { - match bank_debit_data { - hyperswitch_domain_models::payment_method_data::BankDebitData::AchBankDebit { .. } => Self::AchBankDebit, - hyperswitch_domain_models::payment_method_data::BankDebitData::SepaBankDebit { .. } => Self::SepaBankDebit, - hyperswitch_domain_models::payment_method_data::BankDebitData::BecsBankDebit { .. } => Self::BecsBankDebit, - hyperswitch_domain_models::payment_method_data::BankDebitData::BacsBankDebit { .. } => Self::BacsBankDebit, + payment_method_data::BankRedirectData::Bizum {} => Self::Bizum, + payment_method_data::BankRedirectData::Blik { .. } => Self::Blik, + payment_method_data::BankRedirectData::Eps { .. } => Self::Eps, + payment_method_data::BankRedirectData::Giropay { .. } => Self::Giropay, + payment_method_data::BankRedirectData::Ideal { .. } => Self::Ideal, + payment_method_data::BankRedirectData::Interac { .. } => Self::Interac, + payment_method_data::BankRedirectData::OnlineBankingCzechRepublic { .. } => { + Self::OnlineBankingCzechRepublic } - } - PaymentMethodData::BankTransfer(bank_transfer_data) => { - match *bank_transfer_data { - hyperswitch_domain_models::payment_method_data::BankTransferData::AchBankTransfer { .. } => { - Self::AchBankTransfer - } - hyperswitch_domain_models::payment_method_data::BankTransferData::SepaBankTransfer { .. } => { - Self::SepaBankTransfer - } - hyperswitch_domain_models::payment_method_data::BankTransferData::BacsBankTransfer { .. } => { - Self::BacsBankTransfer - } - hyperswitch_domain_models::payment_method_data::BankTransferData::MultibancoBankTransfer { .. } => { - Self::MultibancoBankTransfer - } - hyperswitch_domain_models::payment_method_data::BankTransferData::PermataBankTransfer { .. } => { - Self::PermataBankTransfer - } - hyperswitch_domain_models::payment_method_data::BankTransferData::BcaBankTransfer { .. } => { - Self::BcaBankTransfer - } - hyperswitch_domain_models::payment_method_data::BankTransferData::BniVaBankTransfer { .. } => { - Self::BniVaBankTransfer - } - hyperswitch_domain_models::payment_method_data::BankTransferData::BriVaBankTransfer { .. } => { - Self::BriVaBankTransfer - } - hyperswitch_domain_models::payment_method_data::BankTransferData::CimbVaBankTransfer { .. } => { - Self::CimbVaBankTransfer - } - hyperswitch_domain_models::payment_method_data::BankTransferData::DanamonVaBankTransfer { .. } => { - Self::DanamonVaBankTransfer - } - hyperswitch_domain_models::payment_method_data::BankTransferData::MandiriVaBankTransfer { .. } => { - Self::MandiriVaBankTransfer - } - hyperswitch_domain_models::payment_method_data::BankTransferData::Pix { .. } => Self::Pix, - hyperswitch_domain_models::payment_method_data::BankTransferData::Pse {} => Self::Pse, - hyperswitch_domain_models::payment_method_data::BankTransferData::LocalBankTransfer { .. } => { - Self::LocalBankTransfer - } + payment_method_data::BankRedirectData::OnlineBankingFinland { .. } => { + Self::OnlineBankingFinland } - } + payment_method_data::BankRedirectData::OnlineBankingPoland { .. } => { + Self::OnlineBankingPoland + } + payment_method_data::BankRedirectData::OnlineBankingSlovakia { .. } => { + Self::OnlineBankingSlovakia + } + payment_method_data::BankRedirectData::OpenBankingUk { .. } => Self::OpenBankingUk, + payment_method_data::BankRedirectData::Przelewy24 { .. } => Self::Przelewy24, + payment_method_data::BankRedirectData::Sofort { .. } => Self::Sofort, + payment_method_data::BankRedirectData::Trustly { .. } => Self::Trustly, + payment_method_data::BankRedirectData::OnlineBankingFpx { .. } => { + Self::OnlineBankingFpx + } + payment_method_data::BankRedirectData::OnlineBankingThailand { .. } => { + Self::OnlineBankingThailand + } + payment_method_data::BankRedirectData::LocalBankRedirect {} => { + Self::LocalBankRedirect + } + }, + PaymentMethodData::BankDebit(bank_debit_data) => match bank_debit_data { + payment_method_data::BankDebitData::AchBankDebit { .. } => Self::AchBankDebit, + payment_method_data::BankDebitData::SepaBankDebit { .. } => Self::SepaBankDebit, + payment_method_data::BankDebitData::BecsBankDebit { .. } => Self::BecsBankDebit, + payment_method_data::BankDebitData::BacsBankDebit { .. } => Self::BacsBankDebit, + }, + PaymentMethodData::BankTransfer(bank_transfer_data) => match *bank_transfer_data { + payment_method_data::BankTransferData::AchBankTransfer { .. } => { + Self::AchBankTransfer + } + payment_method_data::BankTransferData::SepaBankTransfer { .. } => { + Self::SepaBankTransfer + } + payment_method_data::BankTransferData::BacsBankTransfer { .. } => { + Self::BacsBankTransfer + } + payment_method_data::BankTransferData::MultibancoBankTransfer { .. } => { + Self::MultibancoBankTransfer + } + payment_method_data::BankTransferData::PermataBankTransfer { .. } => { + Self::PermataBankTransfer + } + payment_method_data::BankTransferData::BcaBankTransfer { .. } => { + Self::BcaBankTransfer + } + payment_method_data::BankTransferData::BniVaBankTransfer { .. } => { + Self::BniVaBankTransfer + } + payment_method_data::BankTransferData::BriVaBankTransfer { .. } => { + Self::BriVaBankTransfer + } + payment_method_data::BankTransferData::CimbVaBankTransfer { .. } => { + Self::CimbVaBankTransfer + } + payment_method_data::BankTransferData::DanamonVaBankTransfer { .. } => { + Self::DanamonVaBankTransfer + } + payment_method_data::BankTransferData::MandiriVaBankTransfer { .. } => { + Self::MandiriVaBankTransfer + } + payment_method_data::BankTransferData::Pix { .. } => Self::Pix, + payment_method_data::BankTransferData::Pse {} => Self::Pse, + payment_method_data::BankTransferData::LocalBankTransfer { .. } => { + Self::LocalBankTransfer + } + }, PaymentMethodData::Crypto(_) => Self::Crypto, PaymentMethodData::MandatePayment => Self::MandatePayment, PaymentMethodData::Reward => Self::Reward, PaymentMethodData::Upi(_) => Self::Upi, PaymentMethodData::Voucher(voucher_data) => match voucher_data { - hyperswitch_domain_models::payment_method_data::VoucherData::Boleto(_) => Self::Boleto, - hyperswitch_domain_models::payment_method_data::VoucherData::Efecty => Self::Efecty, - hyperswitch_domain_models::payment_method_data::VoucherData::PagoEfectivo => Self::PagoEfectivo, - hyperswitch_domain_models::payment_method_data::VoucherData::RedCompra => Self::RedCompra, - hyperswitch_domain_models::payment_method_data::VoucherData::RedPagos => Self::RedPagos, - hyperswitch_domain_models::payment_method_data::VoucherData::Alfamart(_) => Self::Alfamart, - hyperswitch_domain_models::payment_method_data::VoucherData::Indomaret(_) => Self::Indomaret, - hyperswitch_domain_models::payment_method_data::VoucherData::Oxxo => Self::Oxxo, - hyperswitch_domain_models::payment_method_data::VoucherData::SevenEleven(_) => Self::SevenEleven, - hyperswitch_domain_models::payment_method_data::VoucherData::Lawson(_) => Self::Lawson, - hyperswitch_domain_models::payment_method_data::VoucherData::MiniStop(_) => Self::MiniStop, - hyperswitch_domain_models::payment_method_data::VoucherData::FamilyMart(_) => Self::FamilyMart, - hyperswitch_domain_models::payment_method_data::VoucherData::Seicomart(_) => Self::Seicomart, - hyperswitch_domain_models::payment_method_data::VoucherData::PayEasy(_) => Self::PayEasy, - }, - PaymentMethodData::RealTimePayment(real_time_payment_data) => match *real_time_payment_data{ - hyperswitch_domain_models::payment_method_data::RealTimePaymentData::DuitNow { } => Self::DuitNow, - hyperswitch_domain_models::payment_method_data::RealTimePaymentData::Fps { } => Self::Fps, - hyperswitch_domain_models::payment_method_data::RealTimePaymentData::PromptPay { } => Self::PromptPay, - hyperswitch_domain_models::payment_method_data::RealTimePaymentData::VietQr { } => Self::VietQr, + payment_method_data::VoucherData::Boleto(_) => Self::Boleto, + payment_method_data::VoucherData::Efecty => Self::Efecty, + payment_method_data::VoucherData::PagoEfectivo => Self::PagoEfectivo, + payment_method_data::VoucherData::RedCompra => Self::RedCompra, + payment_method_data::VoucherData::RedPagos => Self::RedPagos, + payment_method_data::VoucherData::Alfamart(_) => Self::Alfamart, + payment_method_data::VoucherData::Indomaret(_) => Self::Indomaret, + payment_method_data::VoucherData::Oxxo => Self::Oxxo, + payment_method_data::VoucherData::SevenEleven(_) => Self::SevenEleven, + payment_method_data::VoucherData::Lawson(_) => Self::Lawson, + payment_method_data::VoucherData::MiniStop(_) => Self::MiniStop, + payment_method_data::VoucherData::FamilyMart(_) => Self::FamilyMart, + payment_method_data::VoucherData::Seicomart(_) => Self::Seicomart, + payment_method_data::VoucherData::PayEasy(_) => Self::PayEasy, }, - PaymentMethodData::GiftCard(gift_card_data) => { - match *gift_card_data { - hyperswitch_domain_models::payment_method_data::GiftCardData::Givex(_) => Self::Givex, - hyperswitch_domain_models::payment_method_data::GiftCardData::PaySafeCard {} => Self::PaySafeCar, + PaymentMethodData::RealTimePayment(real_time_payment_data) => { + match *real_time_payment_data { + payment_method_data::RealTimePaymentData::DuitNow {} => Self::DuitNow, + payment_method_data::RealTimePaymentData::Fps {} => Self::Fps, + payment_method_data::RealTimePaymentData::PromptPay {} => Self::PromptPay, + payment_method_data::RealTimePaymentData::VietQr {} => Self::VietQr, } } + PaymentMethodData::GiftCard(gift_card_data) => match *gift_card_data { + payment_method_data::GiftCardData::Givex(_) => Self::Givex, + payment_method_data::GiftCardData::PaySafeCard {} => Self::PaySafeCar, + }, PaymentMethodData::CardToken(_) => Self::CardToken, PaymentMethodData::OpenBanking(data) => match data { - hyperswitch_domain_models::payment_method_data::OpenBankingData::OpenBankingPIS { } => Self::OpenBanking + payment_method_data::OpenBankingData::OpenBankingPIS {} => Self::OpenBanking, + }, + PaymentMethodData::MobilePayment(mobile_payment_data) => match mobile_payment_data { + payment_method_data::MobilePaymentData::DirectCarrierBilling { .. } => { + Self::DirectCarrierBilling + } }, } } } +pub trait ApplePay { + fn get_applepay_decoded_payment_data(&self) -> Result, Error>; +} + +impl ApplePay for payment_method_data::ApplePayWalletData { + fn get_applepay_decoded_payment_data(&self) -> Result, Error> { + let token = Secret::new( + String::from_utf8(BASE64_ENGINE.decode(&self.payment_data).change_context( + errors::ConnectorError::InvalidWalletToken { + wallet_name: "Apple Pay".to_string(), + }, + )?) + .change_context(errors::ConnectorError::InvalidWalletToken { + wallet_name: "Apple Pay".to_string(), + })?, + ); + Ok(token) + } +} + +pub trait WalletData { + fn get_wallet_token(&self) -> Result, Error>; + fn get_wallet_token_as_json(&self, wallet_name: String) -> Result + where + T: serde::de::DeserializeOwned; + fn get_encoded_wallet_token(&self) -> Result; +} + +impl WalletData for payment_method_data::WalletData { + fn get_wallet_token(&self) -> Result, Error> { + match self { + Self::GooglePay(data) => Ok(Secret::new(data.tokenization_data.token.clone())), + Self::ApplePay(data) => Ok(data.get_applepay_decoded_payment_data()?), + Self::PaypalSdk(data) => Ok(Secret::new(data.token.clone())), + _ => Err(errors::ConnectorError::InvalidWallet.into()), + } + } + fn get_wallet_token_as_json(&self, wallet_name: String) -> Result + where + T: serde::de::DeserializeOwned, + { + serde_json::from_str::(self.get_wallet_token()?.peek()) + .change_context(errors::ConnectorError::InvalidWalletToken { wallet_name }) + } + + fn get_encoded_wallet_token(&self) -> Result { + match self { + Self::GooglePay(_) => { + let json_token: Value = self.get_wallet_token_as_json("Google Pay".to_owned())?; + let token_as_vec = serde_json::to_vec(&json_token).change_context( + errors::ConnectorError::InvalidWalletToken { + wallet_name: "Google Pay".to_string(), + }, + )?; + let encoded_token = BASE64_ENGINE.encode(token_as_vec); + Ok(encoded_token) + } + _ => Err( + errors::ConnectorError::NotImplemented("SELECTED PAYMENT METHOD".to_owned()).into(), + ), + } + } +} + +pub fn deserialize_xml_to_struct( + xml_data: &[u8], +) -> Result { + let response_str = std::str::from_utf8(xml_data) + .map_err(|e| { + router_env::logger::error!("Error converting response data to UTF-8: {:?}", e); + errors::ConnectorError::ResponseDeserializationFailed + })? + .trim(); + let result: T = quick_xml::de::from_str(response_str).map_err(|e| { + router_env::logger::error!("Error deserializing XML response: {:?}", e); + errors::ConnectorError::ResponseDeserializationFailed + })?; + + Ok(result) +} diff --git a/crates/hyperswitch_constraint_graph/src/graph.rs b/crates/hyperswitch_constraint_graph/src/graph.rs index 7c435fee2909..8d56406d2a37 100644 --- a/crates/hyperswitch_constraint_graph/src/graph.rs +++ b/crates/hyperswitch_constraint_graph/src/graph.rs @@ -120,7 +120,7 @@ where already_memo .clone() .map_err(|err| GraphError::AnalysisError(Arc::downgrade(&err))) - } else if let Some((initial_strength, initial_relation)) = cycle_map.get(&node_id).cloned() + } else if let Some((initial_strength, initial_relation)) = cycle_map.get(&node_id).copied() { let strength_relation = Strength::get_resolved_strength(initial_strength, strength); let relation_resolve = @@ -197,7 +197,7 @@ where if !unsatisfied.is_empty() { let err = Arc::new(AnalysisTrace::AllAggregation { unsatisfied, - info: self.node_info.get(vald.node_id).cloned().flatten(), + info: self.node_info.get(vald.node_id).copied().flatten(), metadata: self.node_metadata.get(vald.node_id).cloned().flatten(), }); @@ -264,7 +264,7 @@ where } else { let err = Arc::new(AnalysisTrace::AnyAggregation { unsatisfied: unsatisfied.clone(), - info: self.node_info.get(vald.node_id).cloned().flatten(), + info: self.node_info.get(vald.node_id).copied().flatten(), metadata: self.node_metadata.get(vald.node_id).cloned().flatten(), }); @@ -305,7 +305,7 @@ where expected: expected.iter().cloned().collect(), found: None, relation: vald.relation, - info: self.node_info.get(vald.node_id).cloned().flatten(), + info: self.node_info.get(vald.node_id).copied().flatten(), metadata: self.node_metadata.get(vald.node_id).cloned().flatten(), }); @@ -324,7 +324,7 @@ where expected: expected.iter().cloned().collect(), found: Some(ctx_value.clone()), relation: vald.relation, - info: self.node_info.get(vald.node_id).cloned().flatten(), + info: self.node_info.get(vald.node_id).copied().flatten(), metadata: self.node_metadata.get(vald.node_id).cloned().flatten(), }); @@ -402,7 +402,7 @@ where let err = Arc::new(AnalysisTrace::Value { value: val.clone(), relation: vald.relation, - info: self.node_info.get(vald.node_id).cloned().flatten(), + info: self.node_info.get(vald.node_id).copied().flatten(), metadata: self.node_metadata.get(vald.node_id).cloned().flatten(), predecessors: Some(error::ValueTracePredecessor::Mandatory(Box::new( trace.get_analysis_trace()?, @@ -437,7 +437,7 @@ where let err = Arc::new(AnalysisTrace::Value { value: val.clone(), relation: vald.relation, - info: self.node_info.get(vald.node_id).cloned().flatten(), + info: self.node_info.get(vald.node_id).copied().flatten(), metadata: self.node_metadata.get(vald.node_id).cloned().flatten(), predecessors: Some(error::ValueTracePredecessor::OneOf(errors.clone())), }); @@ -469,7 +469,7 @@ where value: val.clone(), relation, predecessors: None, - info: self.node_info.get(node_id).cloned().flatten(), + info: self.node_info.get(node_id).copied().flatten(), metadata: self.node_metadata.get(node_id).cloned().flatten(), }); memo.insert((node_id, relation, strength), Err(Arc::clone(&err))); diff --git a/crates/hyperswitch_domain_models/Cargo.toml b/crates/hyperswitch_domain_models/Cargo.toml index 3a0d4b402b74..ced2151e52b9 100644 --- a/crates/hyperswitch_domain_models/Cargo.toml +++ b/crates/hyperswitch_domain_models/Cargo.toml @@ -13,8 +13,8 @@ encryption_service = [] olap = [] payouts = ["api_models/payouts"] frm = ["api_models/frm"] -v2 = ["api_models/v2", "diesel_models/v2"] -v1 = ["api_models/v1", "diesel_models/v1"] +v2 = ["api_models/v2", "diesel_models/v2", "common_utils/v2"] +v1 = ["api_models/v1", "diesel_models/v1", "common_utils/v1"] customer_v2 = ["api_models/customer_v2", "diesel_models/customer_v2"] payment_methods_v2 = ["api_models/payment_methods_v2", "diesel_models/payment_methods_v2"] @@ -24,6 +24,7 @@ api_models = { version = "0.1.0", path = "../api_models", features = ["errors"] cards = { version = "0.1.0", path = "../cards" } common_enums = { version = "0.1.0", path = "../common_enums" } common_utils = { version = "0.1.0", path = "../common_utils", features = ["async_ext", "metrics", "encryption_service", "keymanager"] } +common_types = { version = "0.1.0", path = "../common_types" } diesel_models = { version = "0.1.0", path = "../diesel_models", features = ["kv_store"], default-features = false } masking = { version = "0.1.0", path = "../masking" } router_derive = { version = "0.1.0", path = "../router_derive" } diff --git a/crates/hyperswitch_domain_models/src/address.rs b/crates/hyperswitch_domain_models/src/address.rs new file mode 100644 index 000000000000..c9beb359c433 --- /dev/null +++ b/crates/hyperswitch_domain_models/src/address.rs @@ -0,0 +1,166 @@ +use masking::{PeekInterface, Secret}; + +#[derive(Default, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct Address { + pub address: Option, + pub phone: Option, + pub email: Option, +} + +impl masking::SerializableSecret for Address {} + +impl Address { + /// Unify the address, giving priority to `self` when details are present in both + pub fn unify_address(&self, other: Option<&Self>) -> Self { + let other_address_details = other.and_then(|address| address.address.as_ref()); + Self { + address: self + .address + .as_ref() + .map(|address| address.unify_address_details(other_address_details)) + .or(other_address_details.cloned()), + email: self + .email + .clone() + .or(other.and_then(|other| other.email.clone())), + phone: self + .phone + .clone() + .or(other.and_then(|other| other.phone.clone())), + } + } +} + +#[derive(Clone, Default, Debug, Eq, serde::Deserialize, serde::Serialize, PartialEq)] +pub struct AddressDetails { + pub city: Option, + pub country: Option, + pub line1: Option>, + pub line2: Option>, + pub line3: Option>, + pub zip: Option>, + pub state: Option>, + pub first_name: Option>, + pub last_name: Option>, +} + +impl AddressDetails { + pub fn get_optional_full_name(&self) -> Option> { + match (self.first_name.as_ref(), self.last_name.as_ref()) { + (Some(first_name), Some(last_name)) => Some(Secret::new(format!( + "{} {}", + first_name.peek(), + last_name.peek() + ))), + (Some(name), None) | (None, Some(name)) => Some(name.to_owned()), + _ => None, + } + } + + /// Unify the address details, giving priority to `self` when details are present in both + pub fn unify_address_details(&self, other: Option<&Self>) -> Self { + if let Some(other) = other { + let (first_name, last_name) = if self + .first_name + .as_ref() + .is_some_and(|first_name| !first_name.peek().trim().is_empty()) + { + (self.first_name.clone(), self.last_name.clone()) + } else { + (other.first_name.clone(), other.last_name.clone()) + }; + + Self { + first_name, + last_name, + city: self.city.clone().or(other.city.clone()), + country: self.country.or(other.country), + line1: self.line1.clone().or(other.line1.clone()), + line2: self.line2.clone().or(other.line2.clone()), + line3: self.line3.clone().or(other.line3.clone()), + zip: self.zip.clone().or(other.zip.clone()), + state: self.state.clone().or(other.state.clone()), + } + } else { + self.clone() + } + } +} + +#[derive(Debug, Clone, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct PhoneDetails { + pub number: Option>, + pub country_code: Option, +} + +impl From for Address { + fn from(address: api_models::payments::Address) -> Self { + Self { + address: address.address.map(AddressDetails::from), + phone: address.phone.map(PhoneDetails::from), + email: address.email, + } + } +} + +impl From for AddressDetails { + fn from(address: api_models::payments::AddressDetails) -> Self { + Self { + city: address.city, + country: address.country, + line1: address.line1, + line2: address.line2, + line3: address.line3, + zip: address.zip, + state: address.state, + first_name: address.first_name, + last_name: address.last_name, + } + } +} + +impl From for PhoneDetails { + fn from(phone: api_models::payments::PhoneDetails) -> Self { + Self { + number: phone.number, + country_code: phone.country_code, + } + } +} + +impl From
for api_models::payments::Address { + fn from(address: Address) -> Self { + Self { + address: address + .address + .map(api_models::payments::AddressDetails::from), + phone: address.phone.map(api_models::payments::PhoneDetails::from), + email: address.email, + } + } +} + +impl From for api_models::payments::AddressDetails { + fn from(address: AddressDetails) -> Self { + Self { + city: address.city, + country: address.country, + line1: address.line1, + line2: address.line2, + line3: address.line3, + zip: address.zip, + state: address.state, + first_name: address.first_name, + last_name: address.last_name, + } + } +} + +impl From for api_models::payments::PhoneDetails { + fn from(phone: PhoneDetails) -> Self { + Self { + number: phone.number, + country_code: phone.country_code, + } + } +} diff --git a/crates/hyperswitch_domain_models/src/business_profile.rs b/crates/hyperswitch_domain_models/src/business_profile.rs index 75cdd40024f8..1df474f18d5f 100644 --- a/crates/hyperswitch_domain_models/src/business_profile.rs +++ b/crates/hyperswitch_domain_models/src/business_profile.rs @@ -58,6 +58,8 @@ pub struct Profile { pub is_network_tokenization_enabled: bool, pub is_auto_retries_enabled: bool, pub max_auto_retries_enabled: Option, + pub is_click_to_pay_enabled: bool, + pub authentication_product_ids: Option, } #[cfg(feature = "v1")] @@ -98,6 +100,8 @@ pub struct ProfileSetter { pub is_network_tokenization_enabled: bool, pub is_auto_retries_enabled: bool, pub max_auto_retries_enabled: Option, + pub is_click_to_pay_enabled: bool, + pub authentication_product_ids: Option, } #[cfg(feature = "v1")] @@ -145,6 +149,8 @@ impl From for Profile { is_network_tokenization_enabled: value.is_network_tokenization_enabled, is_auto_retries_enabled: value.is_auto_retries_enabled, max_auto_retries_enabled: value.max_auto_retries_enabled, + is_click_to_pay_enabled: value.is_click_to_pay_enabled, + authentication_product_ids: value.authentication_product_ids, } } } @@ -194,6 +200,8 @@ pub struct ProfileGeneralUpdate { pub is_network_tokenization_enabled: Option, pub is_auto_retries_enabled: Option, pub max_auto_retries_enabled: Option, + pub is_click_to_pay_enabled: Option, + pub authentication_product_ids: Option, } #[cfg(feature = "v1")] @@ -208,13 +216,13 @@ pub enum ProfileUpdate { dynamic_routing_algorithm: Option, }, ExtendedCardInfoUpdate { - is_extended_card_info_enabled: Option, + is_extended_card_info_enabled: bool, }, ConnectorAgnosticMitUpdate { - is_connector_agnostic_mit_enabled: Option, + is_connector_agnostic_mit_enabled: bool, }, NetworkTokenizationUpdate { - is_network_tokenization_enabled: Option, + is_network_tokenization_enabled: bool, }, } @@ -256,6 +264,8 @@ impl From for ProfileUpdateInternal { is_network_tokenization_enabled, is_auto_retries_enabled, max_auto_retries_enabled, + is_click_to_pay_enabled, + authentication_product_ids, } = *update; Self { @@ -293,6 +303,8 @@ impl From for ProfileUpdateInternal { is_network_tokenization_enabled, is_auto_retries_enabled, max_auto_retries_enabled, + is_click_to_pay_enabled, + authentication_product_ids, } } ProfileUpdate::RoutingAlgorithmUpdate { @@ -332,6 +344,8 @@ impl From for ProfileUpdateInternal { is_network_tokenization_enabled: None, is_auto_retries_enabled: None, max_auto_retries_enabled: None, + is_click_to_pay_enabled: None, + authentication_product_ids: None, }, ProfileUpdate::DynamicRoutingAlgorithmUpdate { dynamic_routing_algorithm, @@ -369,6 +383,8 @@ impl From for ProfileUpdateInternal { is_network_tokenization_enabled: None, is_auto_retries_enabled: None, max_auto_retries_enabled: None, + is_click_to_pay_enabled: None, + authentication_product_ids: None, }, ProfileUpdate::ExtendedCardInfoUpdate { is_extended_card_info_enabled, @@ -391,7 +407,7 @@ impl From for ProfileUpdateInternal { session_expiry: None, authentication_connector_details: None, payout_link_config: None, - is_extended_card_info_enabled, + is_extended_card_info_enabled: Some(is_extended_card_info_enabled), extended_card_info_config: None, is_connector_agnostic_mit_enabled: None, use_billing_as_payment_method_billing: None, @@ -406,6 +422,8 @@ impl From for ProfileUpdateInternal { is_network_tokenization_enabled: None, is_auto_retries_enabled: None, max_auto_retries_enabled: None, + is_click_to_pay_enabled: None, + authentication_product_ids: None, }, ProfileUpdate::ConnectorAgnosticMitUpdate { is_connector_agnostic_mit_enabled, @@ -430,7 +448,7 @@ impl From for ProfileUpdateInternal { payout_link_config: None, is_extended_card_info_enabled: None, extended_card_info_config: None, - is_connector_agnostic_mit_enabled, + is_connector_agnostic_mit_enabled: Some(is_connector_agnostic_mit_enabled), use_billing_as_payment_method_billing: None, collect_shipping_details_from_wallet_connector: None, collect_billing_details_from_wallet_connector: None, @@ -443,6 +461,8 @@ impl From for ProfileUpdateInternal { is_network_tokenization_enabled: None, is_auto_retries_enabled: None, max_auto_retries_enabled: None, + is_click_to_pay_enabled: None, + authentication_product_ids: None, }, ProfileUpdate::NetworkTokenizationUpdate { is_network_tokenization_enabled, @@ -477,9 +497,11 @@ impl From for ProfileUpdateInternal { tax_connector_id: None, is_tax_connector_enabled: None, dynamic_routing_algorithm: None, - is_network_tokenization_enabled, + is_network_tokenization_enabled: Some(is_network_tokenization_enabled), is_auto_retries_enabled: None, max_auto_retries_enabled: None, + is_click_to_pay_enabled: None, + authentication_product_ids: None, }, } } @@ -536,6 +558,8 @@ impl super::behaviour::Conversion for Profile { is_network_tokenization_enabled: self.is_network_tokenization_enabled, is_auto_retries_enabled: Some(self.is_auto_retries_enabled), max_auto_retries_enabled: self.max_auto_retries_enabled, + is_click_to_pay_enabled: self.is_click_to_pay_enabled, + authentication_product_ids: self.authentication_product_ids, }) } @@ -604,6 +628,8 @@ impl super::behaviour::Conversion for Profile { is_network_tokenization_enabled: item.is_network_tokenization_enabled, is_auto_retries_enabled: item.is_auto_retries_enabled.unwrap_or(false), max_auto_retries_enabled: item.max_auto_retries_enabled, + is_click_to_pay_enabled: item.is_click_to_pay_enabled, + authentication_product_ids: item.authentication_product_ids, }) } .await @@ -656,6 +682,8 @@ impl super::behaviour::Conversion for Profile { is_network_tokenization_enabled: self.is_network_tokenization_enabled, is_auto_retries_enabled: Some(self.is_auto_retries_enabled), max_auto_retries_enabled: self.max_auto_retries_enabled, + is_click_to_pay_enabled: self.is_click_to_pay_enabled, + authentication_product_ids: self.authentication_product_ids, }) } } @@ -668,7 +696,7 @@ pub struct Profile { pub profile_name: String, pub created_at: time::PrimitiveDateTime, pub modified_at: time::PrimitiveDateTime, - pub return_url: Option, + pub return_url: Option, pub enable_payment_response_hash: bool, pub payment_response_hash_key: Option, pub redirect_to_merchant_with_http_post: bool, @@ -695,10 +723,13 @@ pub struct Profile { pub frm_routing_algorithm_id: Option, pub payout_routing_algorithm_id: Option, pub default_fallback_routing: Option, + pub should_collect_cvv_during_payment: bool, pub tax_connector_id: Option, pub is_tax_connector_enabled: bool, pub version: common_enums::ApiVersion, pub is_network_tokenization_enabled: bool, + pub is_click_to_pay_enabled: bool, + pub authentication_product_ids: Option, } #[cfg(feature = "v2")] @@ -708,7 +739,7 @@ pub struct ProfileSetter { pub profile_name: String, pub created_at: time::PrimitiveDateTime, pub modified_at: time::PrimitiveDateTime, - pub return_url: Option, + pub return_url: Option, pub enable_payment_response_hash: bool, pub payment_response_hash_key: Option, pub redirect_to_merchant_with_http_post: bool, @@ -735,9 +766,12 @@ pub struct ProfileSetter { pub frm_routing_algorithm_id: Option, pub payout_routing_algorithm_id: Option, pub default_fallback_routing: Option, + pub should_collect_cvv_during_payment: bool, pub tax_connector_id: Option, pub is_tax_connector_enabled: bool, pub is_network_tokenization_enabled: bool, + pub is_click_to_pay_enabled: bool, + pub authentication_product_ids: Option, } #[cfg(feature = "v2")] @@ -780,10 +814,13 @@ impl From for Profile { frm_routing_algorithm_id: value.frm_routing_algorithm_id, payout_routing_algorithm_id: value.payout_routing_algorithm_id, default_fallback_routing: value.default_fallback_routing, + should_collect_cvv_during_payment: value.should_collect_cvv_during_payment, tax_connector_id: value.tax_connector_id, is_tax_connector_enabled: value.is_tax_connector_enabled, version: consts::API_VERSION, is_network_tokenization_enabled: value.is_network_tokenization_enabled, + is_click_to_pay_enabled: value.is_click_to_pay_enabled, + authentication_product_ids: value.authentication_product_ids, } } } @@ -812,7 +849,7 @@ impl Profile { #[derive(Debug)] pub struct ProfileGeneralUpdate { pub profile_name: Option, - pub return_url: Option, + pub return_url: Option, pub enable_payment_response_hash: Option, pub payment_response_hash_key: Option, pub redirect_to_merchant_with_http_post: Option, @@ -834,6 +871,8 @@ pub struct ProfileGeneralUpdate { pub order_fulfillment_time: Option, pub order_fulfillment_time_origin: Option, pub is_network_tokenization_enabled: Option, + pub is_click_to_pay_enabled: Option, + pub authentication_product_ids: Option, } #[cfg(feature = "v2")] @@ -848,13 +887,16 @@ pub enum ProfileUpdate { default_fallback_routing: Option, }, ExtendedCardInfoUpdate { - is_extended_card_info_enabled: Option, + is_extended_card_info_enabled: bool, }, ConnectorAgnosticMitUpdate { - is_connector_agnostic_mit_enabled: Option, + is_connector_agnostic_mit_enabled: bool, }, NetworkTokenizationUpdate { - is_network_tokenization_enabled: Option, + is_network_tokenization_enabled: bool, + }, + CollectCvvDuringPaymentUpdate { + should_collect_cvv_during_payment: bool, }, } @@ -889,6 +931,8 @@ impl From for ProfileUpdateInternal { order_fulfillment_time, order_fulfillment_time_origin, is_network_tokenization_enabled, + is_click_to_pay_enabled, + authentication_product_ids, } = *update; Self { profile_name, @@ -921,11 +965,14 @@ impl From for ProfileUpdateInternal { frm_routing_algorithm_id: None, payout_routing_algorithm_id: None, default_fallback_routing: None, + should_collect_cvv_during_payment: None, tax_connector_id: None, is_tax_connector_enabled: None, is_network_tokenization_enabled, is_auto_retries_enabled: None, max_auto_retries_enabled: None, + is_click_to_pay_enabled: None, + authentication_product_ids, } } ProfileUpdate::RoutingAlgorithmUpdate { @@ -961,11 +1008,14 @@ impl From for ProfileUpdateInternal { frm_routing_algorithm_id: None, payout_routing_algorithm_id, default_fallback_routing: None, + should_collect_cvv_during_payment: None, tax_connector_id: None, is_tax_connector_enabled: None, is_network_tokenization_enabled: None, is_auto_retries_enabled: None, max_auto_retries_enabled: None, + is_click_to_pay_enabled: None, + authentication_product_ids: None, }, ProfileUpdate::ExtendedCardInfoUpdate { is_extended_card_info_enabled, @@ -984,7 +1034,7 @@ impl From for ProfileUpdateInternal { session_expiry: None, authentication_connector_details: None, payout_link_config: None, - is_extended_card_info_enabled, + is_extended_card_info_enabled: Some(is_extended_card_info_enabled), extended_card_info_config: None, is_connector_agnostic_mit_enabled: None, use_billing_as_payment_method_billing: None, @@ -999,11 +1049,14 @@ impl From for ProfileUpdateInternal { order_fulfillment_time_origin: None, frm_routing_algorithm_id: None, default_fallback_routing: None, + should_collect_cvv_during_payment: None, tax_connector_id: None, is_tax_connector_enabled: None, is_network_tokenization_enabled: None, is_auto_retries_enabled: None, max_auto_retries_enabled: None, + is_click_to_pay_enabled: None, + authentication_product_ids: None, }, ProfileUpdate::ConnectorAgnosticMitUpdate { is_connector_agnostic_mit_enabled, @@ -1024,7 +1077,7 @@ impl From for ProfileUpdateInternal { payout_link_config: None, is_extended_card_info_enabled: None, extended_card_info_config: None, - is_connector_agnostic_mit_enabled, + is_connector_agnostic_mit_enabled: Some(is_connector_agnostic_mit_enabled), use_billing_as_payment_method_billing: None, collect_shipping_details_from_wallet_connector: None, collect_billing_details_from_wallet_connector: None, @@ -1037,11 +1090,14 @@ impl From for ProfileUpdateInternal { order_fulfillment_time_origin: None, frm_routing_algorithm_id: None, default_fallback_routing: None, + should_collect_cvv_during_payment: None, tax_connector_id: None, is_tax_connector_enabled: None, is_network_tokenization_enabled: None, is_auto_retries_enabled: None, max_auto_retries_enabled: None, + is_click_to_pay_enabled: None, + authentication_product_ids: None, }, ProfileUpdate::DefaultRoutingFallbackUpdate { default_fallback_routing, @@ -1075,11 +1131,14 @@ impl From for ProfileUpdateInternal { order_fulfillment_time_origin: None, frm_routing_algorithm_id: None, default_fallback_routing, + should_collect_cvv_during_payment: None, tax_connector_id: None, is_tax_connector_enabled: None, is_network_tokenization_enabled: None, is_auto_retries_enabled: None, max_auto_retries_enabled: None, + is_click_to_pay_enabled: None, + authentication_product_ids: None, }, ProfileUpdate::NetworkTokenizationUpdate { is_network_tokenization_enabled, @@ -1113,11 +1172,55 @@ impl From for ProfileUpdateInternal { order_fulfillment_time_origin: None, frm_routing_algorithm_id: None, default_fallback_routing: None, + should_collect_cvv_during_payment: None, tax_connector_id: None, is_tax_connector_enabled: None, - is_network_tokenization_enabled, + is_network_tokenization_enabled: Some(is_network_tokenization_enabled), + is_auto_retries_enabled: None, + max_auto_retries_enabled: None, + is_click_to_pay_enabled: None, + authentication_product_ids: None, + }, + ProfileUpdate::CollectCvvDuringPaymentUpdate { + should_collect_cvv_during_payment, + } => Self { + profile_name: None, + modified_at: now, + return_url: None, + enable_payment_response_hash: None, + payment_response_hash_key: None, + redirect_to_merchant_with_http_post: None, + webhook_details: None, + metadata: None, + is_recon_enabled: None, + applepay_verified_domains: None, + payment_link_config: None, + session_expiry: None, + authentication_connector_details: None, + payout_link_config: None, + is_extended_card_info_enabled: None, + extended_card_info_config: None, + is_connector_agnostic_mit_enabled: None, + use_billing_as_payment_method_billing: None, + collect_shipping_details_from_wallet_connector: None, + collect_billing_details_from_wallet_connector: None, + outgoing_webhook_custom_http_headers: None, + always_collect_billing_details_from_wallet_connector: None, + always_collect_shipping_details_from_wallet_connector: None, + routing_algorithm_id: None, + payout_routing_algorithm_id: None, + order_fulfillment_time: None, + order_fulfillment_time_origin: None, + frm_routing_algorithm_id: None, + default_fallback_routing: None, + should_collect_cvv_during_payment: Some(should_collect_cvv_during_payment), + tax_connector_id: None, + is_tax_connector_enabled: None, + is_network_tokenization_enabled: None, is_auto_retries_enabled: None, max_auto_retries_enabled: None, + is_click_to_pay_enabled: None, + authentication_product_ids: None, }, } } @@ -1169,6 +1272,7 @@ impl super::behaviour::Conversion for Profile { order_fulfillment_time_origin: self.order_fulfillment_time_origin, frm_routing_algorithm_id: self.frm_routing_algorithm_id, default_fallback_routing: self.default_fallback_routing, + should_collect_cvv_during_payment: self.should_collect_cvv_during_payment, tax_connector_id: self.tax_connector_id, is_tax_connector_enabled: Some(self.is_tax_connector_enabled), version: self.version, @@ -1176,6 +1280,8 @@ impl super::behaviour::Conversion for Profile { is_network_tokenization_enabled: self.is_network_tokenization_enabled, is_auto_retries_enabled: None, max_auto_retries_enabled: None, + is_click_to_pay_enabled: self.is_click_to_pay_enabled, + authentication_product_ids: self.authentication_product_ids, }) } @@ -1239,10 +1345,13 @@ impl super::behaviour::Conversion for Profile { frm_routing_algorithm_id: item.frm_routing_algorithm_id, payout_routing_algorithm_id: item.payout_routing_algorithm_id, default_fallback_routing: item.default_fallback_routing, + should_collect_cvv_during_payment: item.should_collect_cvv_during_payment, tax_connector_id: item.tax_connector_id, is_tax_connector_enabled: item.is_tax_connector_enabled.unwrap_or(false), version: item.version, is_network_tokenization_enabled: item.is_network_tokenization_enabled, + is_click_to_pay_enabled: item.is_click_to_pay_enabled, + authentication_product_ids: item.authentication_product_ids, }) } .await @@ -1291,12 +1400,15 @@ impl super::behaviour::Conversion for Profile { frm_routing_algorithm_id: self.frm_routing_algorithm_id, payout_routing_algorithm_id: self.payout_routing_algorithm_id, default_fallback_routing: self.default_fallback_routing, + should_collect_cvv_during_payment: self.should_collect_cvv_during_payment, tax_connector_id: self.tax_connector_id, is_tax_connector_enabled: Some(self.is_tax_connector_enabled), version: self.version, is_network_tokenization_enabled: self.is_network_tokenization_enabled, is_auto_retries_enabled: None, max_auto_retries_enabled: None, + is_click_to_pay_enabled: self.is_click_to_pay_enabled, + authentication_product_ids: self.authentication_product_ids, }) } } diff --git a/crates/hyperswitch_domain_models/src/customer.rs b/crates/hyperswitch_domain_models/src/customer.rs index af901a80558e..b503326f84c5 100644 --- a/crates/hyperswitch_domain_models/src/customer.rs +++ b/crates/hyperswitch_domain_models/src/customer.rs @@ -1,8 +1,8 @@ -use api_models::customers::CustomerRequestWithEncryption; #[cfg(all(feature = "v2", feature = "customer_v2"))] use common_enums::DeleteStatus; use common_utils::{ - crypto, date_time, + crypto::{self, Encryptable}, + date_time, encryption::Encryption, errors::{CustomResult, ValidationError}, id_type, pii, @@ -13,19 +13,23 @@ use common_utils::{ }; use diesel_models::customers::CustomerUpdateInternal; use error_stack::ResultExt; -use masking::{PeekInterface, Secret}; +use masking::{PeekInterface, Secret, SwitchStrategy}; +use rustc_hash::FxHashMap; use time::PrimitiveDateTime; use crate::type_encryption as types; #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, router_derive::ToEncryption)] pub struct Customer { pub customer_id: id_type::CustomerId, pub merchant_id: id_type::MerchantId, - pub name: crypto::OptionalEncryptableName, - pub email: crypto::OptionalEncryptableEmail, - pub phone: crypto::OptionalEncryptablePhone, + #[encrypt] + pub name: Option>>, + #[encrypt] + pub email: Option>>, + #[encrypt] + pub phone: Option>>, pub phone_country_code: Option, pub description: Option, pub created_at: PrimitiveDateTime, @@ -39,12 +43,15 @@ pub struct Customer { } #[cfg(all(feature = "v2", feature = "customer_v2"))] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, router_derive::ToEncryption)] pub struct Customer { pub merchant_id: id_type::MerchantId, - pub name: crypto::OptionalEncryptableName, - pub email: crypto::OptionalEncryptableEmail, - pub phone: crypto::OptionalEncryptablePhone, + #[encrypt] + pub name: Option>>, + #[encrypt] + pub email: Option>>, + #[encrypt] + pub phone: Option>>, pub phone_country_code: Option, pub description: Option, pub created_at: PrimitiveDateTime, @@ -56,11 +63,25 @@ pub struct Customer { pub merchant_reference_id: Option, pub default_billing_address: Option, pub default_shipping_address: Option, - pub id: String, + pub id: id_type::GlobalCustomerId, pub version: common_enums::ApiVersion, pub status: DeleteStatus, } +impl Customer { + /// Get the unique identifier of Customer + #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] + pub fn get_id(&self) -> &id_type::CustomerId { + &self.customer_id + } + + /// Get the global identifier of Customer + #[cfg(all(feature = "v2", feature = "customer_v2"))] + pub fn get_id(&self) -> &id_type::GlobalCustomerId { + &self.id + } +} + #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] #[async_trait::async_trait] impl super::behaviour::Conversion for Customer { @@ -98,8 +119,8 @@ impl super::behaviour::Conversion for Customer { let decrypted = types::crypto_operation( state, common_utils::type_name!(Self::DstType), - types::CryptoOperation::BatchDecrypt(CustomerRequestWithEncryption::to_encryptable( - CustomerRequestWithEncryption { + types::CryptoOperation::BatchDecrypt(EncryptedCustomer::to_encryptable( + EncryptedCustomer { name: item.name.clone(), phone: item.phone.clone(), email: item.email.clone(), @@ -113,16 +134,23 @@ impl super::behaviour::Conversion for Customer { .change_context(ValidationError::InvalidValue { message: "Failed while decrypting customer data".to_string(), })?; - let encryptable_customer = CustomerRequestWithEncryption::from_encryptable(decrypted) - .change_context(ValidationError::InvalidValue { + let encryptable_customer = EncryptedCustomer::from_encryptable(decrypted).change_context( + ValidationError::InvalidValue { message: "Failed while decrypting customer data".to_string(), - })?; + }, + )?; Ok(Self { customer_id: item.customer_id, merchant_id: item.merchant_id, name: encryptable_customer.name, - email: encryptable_customer.email, + email: encryptable_customer.email.map(|email| { + let encryptable: Encryptable> = Encryptable::new( + email.clone().into_inner().switch_strategy(), + email.into_encrypted(), + ); + encryptable + }), phone: encryptable_customer.phone, phone_country_code: item.phone_country_code, description: item.description, @@ -198,8 +226,8 @@ impl super::behaviour::Conversion for Customer { let decrypted = types::crypto_operation( state, common_utils::type_name!(Self::DstType), - types::CryptoOperation::BatchDecrypt(CustomerRequestWithEncryption::to_encryptable( - CustomerRequestWithEncryption { + types::CryptoOperation::BatchDecrypt(EncryptedCustomer::to_encryptable( + EncryptedCustomer { name: item.name.clone(), phone: item.phone.clone(), email: item.email.clone(), @@ -213,17 +241,24 @@ impl super::behaviour::Conversion for Customer { .change_context(ValidationError::InvalidValue { message: "Failed while decrypting customer data".to_string(), })?; - let encryptable_customer = CustomerRequestWithEncryption::from_encryptable(decrypted) - .change_context(ValidationError::InvalidValue { + let encryptable_customer = EncryptedCustomer::from_encryptable(decrypted).change_context( + ValidationError::InvalidValue { message: "Failed while decrypting customer data".to_string(), - })?; + }, + )?; Ok(Self { id: item.id, merchant_reference_id: item.merchant_reference_id, merchant_id: item.merchant_id, name: encryptable_customer.name, - email: encryptable_customer.email, + email: encryptable_customer.email.map(|email| { + let encryptable: Encryptable> = Encryptable::new( + email.clone().into_inner().switch_strategy(), + email.into_encrypted(), + ); + encryptable + }), phone: encryptable_customer.phone, phone_country_code: item.phone_country_code, description: item.description, @@ -275,7 +310,7 @@ pub enum CustomerUpdate { description: Option, phone_country_code: Option, metadata: Option, - connector_customer: Option, + connector_customer: Box>, default_billing_address: Option, default_shipping_address: Option, default_payment_method_id: Option>, @@ -312,7 +347,7 @@ impl From for CustomerUpdateInternal { description, phone_country_code, metadata, - connector_customer, + connector_customer: *connector_customer, modified_at: date_time::now(), default_billing_address, default_shipping_address, @@ -366,7 +401,7 @@ pub enum CustomerUpdate { description: Option, phone_country_code: Option, metadata: Option, - connector_customer: Option, + connector_customer: Box>, address_id: Option, }, ConnectorCustomer { @@ -397,7 +432,7 @@ impl From for CustomerUpdateInternal { description, phone_country_code, metadata, - connector_customer, + connector_customer: *connector_customer, modified_at: date_time::now(), address_id, default_payment_method_id: None, diff --git a/crates/hyperswitch_domain_models/src/errors/api_error_response.rs b/crates/hyperswitch_domain_models/src/errors/api_error_response.rs index 75bd7740026f..6fa9f9450c85 100644 --- a/crates/hyperswitch_domain_models/src/errors/api_error_response.rs +++ b/crates/hyperswitch_domain_models/src/errors/api_error_response.rs @@ -274,7 +274,15 @@ pub enum ApiErrorResponse { LinkConfigurationError { message: String }, #[error(error_type = ErrorType::InvalidRequestError, code = "IR_41", message = "Payout validation failed")] PayoutFailed { data: Option }, - + #[error( + error_type = ErrorType::InvalidRequestError, code = "IR_42", + message = "Cookies are not found in the request" + )] + CookieNotFound, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_43", message = "API does not support platform account operation")] + PlatformAccountAuthNotSupported, + #[error(error_type = ErrorType::InvalidRequestError, code = "IR_44", message = "Invalid platform account operation")] + InvalidPlatformOperation, #[error(error_type = ErrorType::InvalidRequestError, code = "WE_01", message = "Failed to authenticate the webhook")] WebhookAuthenticationFailed, #[error(error_type = ErrorType::InvalidRequestError, code = "WE_02", message = "Bad request received in webhook")] @@ -627,6 +635,9 @@ impl ErrorSwitch for ApiErrorRespon Self::PayoutFailed { data } => { AER::BadRequest(ApiError::new("IR", 41, "Payout failed while processing with connector.", Some(Extra { data: data.clone(), ..Default::default()}))) }, + Self::CookieNotFound => { + AER::Unauthorized(ApiError::new("IR", 42, "Cookies are not found in the request", None)) + }, Self::WebhookAuthenticationFailed => { AER::Unauthorized(ApiError::new("WE", 1, "Webhook authentication failed", None)) @@ -644,7 +655,7 @@ impl ErrorSwitch for ApiErrorRespon AER::Unprocessable(ApiError::new("WE", 5, "There was an issue processing the webhook body", None)) }, Self::WebhookInvalidMerchantSecret => { - AER::BadRequest(ApiError::new("WE", 6, "Merchant Secret set for webhook source verificartion is invalid", None)) + AER::BadRequest(ApiError::new("WE", 6, "Merchant Secret set for webhook source verification is invalid", None)) } Self::IntegrityCheckFailed { reason, @@ -659,6 +670,12 @@ impl ErrorSwitch for ApiErrorRespon ..Default::default() }) )), + Self::PlatformAccountAuthNotSupported => { + AER::BadRequest(ApiError::new("IR", 43, "API does not support platform operation", None)) + } + Self::InvalidPlatformOperation => { + AER::Unauthorized(ApiError::new("IR", 44, "Invalid platform account operation", None)) + } } } } diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index c99fbd89cd54..7a170f918efc 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -1,3 +1,4 @@ +pub mod address; pub mod api; pub mod behaviour; pub mod business_profile; @@ -16,6 +17,7 @@ pub mod payments; #[cfg(feature = "payouts")] pub mod payouts; pub mod refunds; +pub mod relay; pub mod router_data; pub mod router_data_v2; pub mod router_flow_types; @@ -30,6 +32,18 @@ pub trait PayoutAttemptInterface {} #[cfg(not(feature = "payouts"))] pub trait PayoutsInterface {} +use api_models::payments::{ + ApplePayRecurringDetails as ApiApplePayRecurringDetails, + ApplePayRegularBillingDetails as ApiApplePayRegularBillingDetails, + FeatureMetadata as ApiFeatureMetadata, OrderDetailsWithAmount as ApiOrderDetailsWithAmount, + RecurringPaymentIntervalUnit as ApiRecurringPaymentIntervalUnit, + RedirectResponse as ApiRedirectResponse, +}; +use diesel_models::types::{ + ApplePayRecurringDetails, ApplePayRegularBillingDetails, FeatureMetadata, + OrderDetailsWithAmount, RecurringPaymentIntervalUnit, RedirectResponse, +}; + #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] pub enum RemoteStorageObject { ForeignID(String), @@ -54,3 +68,377 @@ impl RemoteStorageObject { } } } + +use std::fmt::Debug; + +pub trait ApiModelToDieselModelConvertor { + /// Convert from a foreign type to the current type + fn convert_from(from: F) -> Self; + fn convert_back(self) -> F; +} + +impl ApiModelToDieselModelConvertor for FeatureMetadata { + fn convert_from(from: ApiFeatureMetadata) -> Self { + let ApiFeatureMetadata { + redirect_response, + search_tags, + apple_pay_recurring_details, + } = from; + Self { + redirect_response: redirect_response.map(RedirectResponse::convert_from), + search_tags, + apple_pay_recurring_details: apple_pay_recurring_details + .map(ApplePayRecurringDetails::convert_from), + } + } + + fn convert_back(self) -> ApiFeatureMetadata { + let Self { + redirect_response, + search_tags, + apple_pay_recurring_details, + } = self; + ApiFeatureMetadata { + redirect_response: redirect_response + .map(|redirect_response| redirect_response.convert_back()), + search_tags, + apple_pay_recurring_details: apple_pay_recurring_details + .map(|value| value.convert_back()), + } + } +} + +impl ApiModelToDieselModelConvertor for RedirectResponse { + fn convert_from(from: ApiRedirectResponse) -> Self { + let ApiRedirectResponse { + param, + json_payload, + } = from; + Self { + param, + json_payload, + } + } + + fn convert_back(self) -> ApiRedirectResponse { + let Self { + param, + json_payload, + } = self; + ApiRedirectResponse { + param, + json_payload, + } + } +} + +impl ApiModelToDieselModelConvertor + for RecurringPaymentIntervalUnit +{ + fn convert_from(from: ApiRecurringPaymentIntervalUnit) -> Self { + match from { + ApiRecurringPaymentIntervalUnit::Year => Self::Year, + ApiRecurringPaymentIntervalUnit::Month => Self::Month, + ApiRecurringPaymentIntervalUnit::Day => Self::Day, + ApiRecurringPaymentIntervalUnit::Hour => Self::Hour, + ApiRecurringPaymentIntervalUnit::Minute => Self::Minute, + } + } + fn convert_back(self) -> ApiRecurringPaymentIntervalUnit { + match self { + Self::Year => ApiRecurringPaymentIntervalUnit::Year, + Self::Month => ApiRecurringPaymentIntervalUnit::Month, + Self::Day => ApiRecurringPaymentIntervalUnit::Day, + Self::Hour => ApiRecurringPaymentIntervalUnit::Hour, + Self::Minute => ApiRecurringPaymentIntervalUnit::Minute, + } + } +} + +impl ApiModelToDieselModelConvertor + for ApplePayRegularBillingDetails +{ + fn convert_from(from: ApiApplePayRegularBillingDetails) -> Self { + Self { + label: from.label, + recurring_payment_start_date: from.recurring_payment_start_date, + recurring_payment_end_date: from.recurring_payment_end_date, + recurring_payment_interval_unit: from + .recurring_payment_interval_unit + .map(RecurringPaymentIntervalUnit::convert_from), + recurring_payment_interval_count: from.recurring_payment_interval_count, + } + } + + fn convert_back(self) -> ApiApplePayRegularBillingDetails { + ApiApplePayRegularBillingDetails { + label: self.label, + recurring_payment_start_date: self.recurring_payment_start_date, + recurring_payment_end_date: self.recurring_payment_end_date, + recurring_payment_interval_unit: self + .recurring_payment_interval_unit + .map(|value| value.convert_back()), + recurring_payment_interval_count: self.recurring_payment_interval_count, + } + } +} + +impl ApiModelToDieselModelConvertor for ApplePayRecurringDetails { + fn convert_from(from: ApiApplePayRecurringDetails) -> Self { + Self { + payment_description: from.payment_description, + regular_billing: ApplePayRegularBillingDetails::convert_from(from.regular_billing), + billing_agreement: from.billing_agreement, + management_url: from.management_url, + } + } + + fn convert_back(self) -> ApiApplePayRecurringDetails { + ApiApplePayRecurringDetails { + payment_description: self.payment_description, + regular_billing: self.regular_billing.convert_back(), + billing_agreement: self.billing_agreement, + management_url: self.management_url, + } + } +} + +impl ApiModelToDieselModelConvertor for OrderDetailsWithAmount { + fn convert_from(from: ApiOrderDetailsWithAmount) -> Self { + let ApiOrderDetailsWithAmount { + product_name, + quantity, + amount, + requires_shipping, + product_img_link, + product_id, + category, + sub_category, + brand, + product_type, + product_tax_code, + tax_rate, + total_tax_amount, + } = from; + Self { + product_name, + quantity, + amount, + requires_shipping, + product_img_link, + product_id, + category, + sub_category, + brand, + product_type, + product_tax_code, + tax_rate, + total_tax_amount, + } + } + + fn convert_back(self) -> ApiOrderDetailsWithAmount { + let Self { + product_name, + quantity, + amount, + requires_shipping, + product_img_link, + product_id, + category, + sub_category, + brand, + product_type, + product_tax_code, + tax_rate, + total_tax_amount, + } = self; + ApiOrderDetailsWithAmount { + product_name, + quantity, + amount, + requires_shipping, + product_img_link, + product_id, + category, + sub_category, + brand, + product_type, + product_tax_code, + tax_rate, + total_tax_amount, + } + } +} + +#[cfg(feature = "v2")] +impl ApiModelToDieselModelConvertor + for diesel_models::payment_intent::PaymentLinkConfigRequestForPayments +{ + fn convert_from(item: api_models::admin::PaymentLinkConfigRequest) -> Self { + Self { + theme: item.theme, + logo: item.logo, + seller_name: item.seller_name, + sdk_layout: item.sdk_layout, + display_sdk_only: item.display_sdk_only, + enabled_saved_payment_method: item.enabled_saved_payment_method, + hide_card_nickname_field: item.hide_card_nickname_field, + show_card_form_by_default: item.show_card_form_by_default, + details_layout: item.details_layout, + transaction_details: item.transaction_details.map(|transaction_details| { + transaction_details + .into_iter() + .map(|transaction_detail| { + diesel_models::PaymentLinkTransactionDetails::convert_from( + transaction_detail, + ) + }) + .collect() + }), + background_image: item.background_image.map(|background_image| { + diesel_models::business_profile::PaymentLinkBackgroundImageConfig::convert_from( + background_image, + ) + }), + payment_button_text: item.payment_button_text, + } + } + fn convert_back(self) -> api_models::admin::PaymentLinkConfigRequest { + let Self { + theme, + logo, + seller_name, + sdk_layout, + display_sdk_only, + enabled_saved_payment_method, + hide_card_nickname_field, + show_card_form_by_default, + transaction_details, + background_image, + details_layout, + payment_button_text, + } = self; + api_models::admin::PaymentLinkConfigRequest { + theme, + logo, + seller_name, + sdk_layout, + display_sdk_only, + enabled_saved_payment_method, + hide_card_nickname_field, + show_card_form_by_default, + details_layout, + transaction_details: transaction_details.map(|transaction_details| { + transaction_details + .into_iter() + .map(|transaction_detail| transaction_detail.convert_back()) + .collect() + }), + background_image: background_image + .map(|background_image| background_image.convert_back()), + payment_button_text, + } + } +} + +#[cfg(feature = "v2")] +impl ApiModelToDieselModelConvertor + for diesel_models::PaymentLinkTransactionDetails +{ + fn convert_from(from: api_models::admin::PaymentLinkTransactionDetails) -> Self { + Self { + key: from.key, + value: from.value, + ui_configuration: from + .ui_configuration + .map(diesel_models::TransactionDetailsUiConfiguration::convert_from), + } + } + fn convert_back(self) -> api_models::admin::PaymentLinkTransactionDetails { + let Self { + key, + value, + ui_configuration, + } = self; + api_models::admin::PaymentLinkTransactionDetails { + key, + value, + ui_configuration: ui_configuration + .map(|ui_configuration| ui_configuration.convert_back()), + } + } +} + +#[cfg(feature = "v2")] +impl ApiModelToDieselModelConvertor + for diesel_models::business_profile::PaymentLinkBackgroundImageConfig +{ + fn convert_from(from: api_models::admin::PaymentLinkBackgroundImageConfig) -> Self { + Self { + url: from.url, + position: from.position, + size: from.size, + } + } + fn convert_back(self) -> api_models::admin::PaymentLinkBackgroundImageConfig { + let Self { + url, + position, + size, + } = self; + api_models::admin::PaymentLinkBackgroundImageConfig { + url, + position, + size, + } + } +} + +#[cfg(feature = "v2")] +impl ApiModelToDieselModelConvertor + for diesel_models::TransactionDetailsUiConfiguration +{ + fn convert_from(from: api_models::admin::TransactionDetailsUiConfiguration) -> Self { + Self { + position: from.position, + is_key_bold: from.is_key_bold, + is_value_bold: from.is_value_bold, + } + } + fn convert_back(self) -> api_models::admin::TransactionDetailsUiConfiguration { + let Self { + position, + is_key_bold, + is_value_bold, + } = self; + api_models::admin::TransactionDetailsUiConfiguration { + position, + is_key_bold, + is_value_bold, + } + } +} + +#[cfg(feature = "v2")] +impl From for payments::AmountDetails { + fn from(amount_details: api_models::payments::AmountDetails) -> Self { + Self { + order_amount: amount_details.order_amount().into(), + currency: amount_details.currency(), + shipping_cost: amount_details.shipping_cost(), + tax_details: amount_details.order_tax_amount().map(|order_tax_amount| { + diesel_models::TaxDetails { + default: Some(diesel_models::DefaultTax { order_tax_amount }), + payment_method_type: None, + } + }), + skip_external_tax_calculation: amount_details.skip_external_tax_calculation(), + skip_surcharge_calculation: amount_details.skip_surcharge_calculation(), + surcharge_amount: amount_details.surcharge_amount(), + tax_on_surcharge: amount_details.tax_on_surcharge(), + // We will not receive this in the request. This will be populated after calling the connector / processor + amount_captured: None, + } + } +} diff --git a/crates/hyperswitch_domain_models/src/merchant_account.rs b/crates/hyperswitch_domain_models/src/merchant_account.rs index 3e860d362845..c42b0005513f 100644 --- a/crates/hyperswitch_domain_models/src/merchant_account.rs +++ b/crates/hyperswitch_domain_models/src/merchant_account.rs @@ -47,6 +47,7 @@ pub struct MerchantAccount { pub payment_link_config: Option, pub pm_collect_link_config: Option, pub version: common_enums::ApiVersion, + pub is_platform_account: bool, } #[cfg(feature = "v1")] @@ -81,6 +82,7 @@ pub struct MerchantAccountSetter { pub payment_link_config: Option, pub pm_collect_link_config: Option, pub version: common_enums::ApiVersion, + pub is_platform_account: bool, } #[cfg(feature = "v1")] @@ -115,6 +117,7 @@ impl From for MerchantAccount { payment_link_config: item.payment_link_config, pm_collect_link_config: item.pm_collect_link_config, version: item.version, + is_platform_account: item.is_platform_account, } } } @@ -133,6 +136,7 @@ pub struct MerchantAccountSetter { pub modified_at: time::PrimitiveDateTime, pub organization_id: common_utils::id_type::OrganizationId, pub recon_status: diesel_models::enums::ReconStatus, + pub is_platform_account: bool, } #[cfg(feature = "v2")] @@ -149,6 +153,7 @@ impl From for MerchantAccount { modified_at, organization_id, recon_status, + is_platform_account, } = item; Self { id, @@ -161,6 +166,7 @@ impl From for MerchantAccount { modified_at, organization_id, recon_status, + is_platform_account, } } } @@ -178,6 +184,7 @@ pub struct MerchantAccount { pub modified_at: time::PrimitiveDateTime, pub organization_id: common_utils::id_type::OrganizationId, pub recon_status: diesel_models::enums::ReconStatus, + pub is_platform_account: bool, } impl MerchantAccount { @@ -201,7 +208,7 @@ impl MerchantAccount { #[cfg(feature = "v1")] #[allow(clippy::large_enum_variant)] -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum MerchantAccountUpdate { Update { merchant_name: OptionalEncryptableName, @@ -233,11 +240,12 @@ pub enum MerchantAccountUpdate { }, UnsetDefaultProfile, ModifiedAtUpdate, + ToPlatformAccount, } #[cfg(feature = "v2")] #[allow(clippy::large_enum_variant)] -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum MerchantAccountUpdate { Update { merchant_name: OptionalEncryptableName, @@ -252,10 +260,10 @@ pub enum MerchantAccountUpdate { recon_status: diesel_models::enums::ReconStatus, }, ModifiedAtUpdate, + ToPlatformAccount, } #[cfg(feature = "v1")] - impl From for MerchantAccountUpdateInternal { fn from(merchant_account_update: MerchantAccountUpdate) -> Self { let now = date_time::now(); @@ -308,6 +316,7 @@ impl From for MerchantAccountUpdateInternal { organization_id: None, is_recon_enabled: None, recon_status: None, + is_platform_account: None, }, MerchantAccountUpdate::StorageSchemeUpdate { storage_scheme } => Self { storage_scheme: Some(storage_scheme), @@ -335,6 +344,7 @@ impl From for MerchantAccountUpdateInternal { recon_status: None, payment_link_config: None, pm_collect_link_config: None, + is_platform_account: None, }, MerchantAccountUpdate::ReconUpdate { recon_status } => Self { recon_status: Some(recon_status), @@ -362,6 +372,7 @@ impl From for MerchantAccountUpdateInternal { default_profile: None, payment_link_config: None, pm_collect_link_config: None, + is_platform_account: None, }, MerchantAccountUpdate::UnsetDefaultProfile => Self { default_profile: Some(None), @@ -389,6 +400,7 @@ impl From for MerchantAccountUpdateInternal { recon_status: None, payment_link_config: None, pm_collect_link_config: None, + is_platform_account: None, }, MerchantAccountUpdate::ModifiedAtUpdate => Self { modified_at: now, @@ -416,6 +428,35 @@ impl From for MerchantAccountUpdateInternal { recon_status: None, payment_link_config: None, pm_collect_link_config: None, + is_platform_account: None, + }, + MerchantAccountUpdate::ToPlatformAccount => Self { + modified_at: now, + merchant_name: None, + merchant_details: None, + return_url: None, + webhook_details: None, + sub_merchants_enabled: None, + parent_merchant_id: None, + enable_payment_response_hash: None, + payment_response_hash_key: None, + redirect_to_merchant_with_http_post: None, + publishable_key: None, + storage_scheme: None, + locker_id: None, + metadata: None, + routing_algorithm: None, + primary_business_details: None, + intent_fulfillment_time: None, + frm_routing_algorithm: None, + payout_routing_algorithm: None, + organization_id: None, + is_recon_enabled: None, + default_profile: None, + recon_status: None, + payment_link_config: None, + pm_collect_link_config: None, + is_platform_account: Some(true), }, } } @@ -441,6 +482,7 @@ impl From for MerchantAccountUpdateInternal { storage_scheme: None, organization_id: None, recon_status: None, + is_platform_account: None, }, MerchantAccountUpdate::StorageSchemeUpdate { storage_scheme } => Self { storage_scheme: Some(storage_scheme), @@ -451,6 +493,7 @@ impl From for MerchantAccountUpdateInternal { metadata: None, organization_id: None, recon_status: None, + is_platform_account: None, }, MerchantAccountUpdate::ReconUpdate { recon_status } => Self { recon_status: Some(recon_status), @@ -461,6 +504,7 @@ impl From for MerchantAccountUpdateInternal { storage_scheme: None, metadata: None, organization_id: None, + is_platform_account: None, }, MerchantAccountUpdate::ModifiedAtUpdate => Self { modified_at: now, @@ -471,6 +515,18 @@ impl From for MerchantAccountUpdateInternal { metadata: None, organization_id: None, recon_status: None, + is_platform_account: None, + }, + MerchantAccountUpdate::ToPlatformAccount => Self { + modified_at: now, + merchant_name: None, + merchant_details: None, + publishable_key: None, + storage_scheme: None, + metadata: None, + organization_id: None, + recon_status: None, + is_platform_account: Some(true), }, } } @@ -496,6 +552,7 @@ impl super::behaviour::Conversion for MerchantAccount { organization_id: self.organization_id, recon_status: self.recon_status, version: crate::consts::API_VERSION, + is_platform_account: self.is_platform_account, }; Ok(diesel_models::MerchantAccount::from(setter)) @@ -555,6 +612,7 @@ impl super::behaviour::Conversion for MerchantAccount { modified_at: item.modified_at, organization_id: item.organization_id, recon_status: item.recon_status, + is_platform_account: item.is_platform_account, }) } .await @@ -576,6 +634,7 @@ impl super::behaviour::Conversion for MerchantAccount { organization_id: self.organization_id, recon_status: self.recon_status, version: crate::consts::API_VERSION, + is_platform_account: self.is_platform_account, }) } } @@ -615,6 +674,7 @@ impl super::behaviour::Conversion for MerchantAccount { payment_link_config: self.payment_link_config, pm_collect_link_config: self.pm_collect_link_config, version: self.version, + is_platform_account: self.is_platform_account, }; Ok(diesel_models::MerchantAccount::from(setter)) @@ -692,6 +752,7 @@ impl super::behaviour::Conversion for MerchantAccount { payment_link_config: item.payment_link_config, pm_collect_link_config: item.pm_collect_link_config, version: item.version, + is_platform_account: item.is_platform_account, }) } .await @@ -730,6 +791,7 @@ impl super::behaviour::Conversion for MerchantAccount { payment_link_config: self.payment_link_config, pm_collect_link_config: self.pm_collect_link_config, version: crate::consts::API_VERSION, + is_platform_account: self.is_platform_account, }) } } diff --git a/crates/hyperswitch_domain_models/src/merchant_connector_account.rs b/crates/hyperswitch_domain_models/src/merchant_connector_account.rs index a281c553a133..7789c05e9902 100644 --- a/crates/hyperswitch_domain_models/src/merchant_connector_account.rs +++ b/crates/hyperswitch_domain_models/src/merchant_connector_account.rs @@ -1,24 +1,37 @@ +#[cfg(feature = "v2")] +use api_models::admin; use common_utils::{ crypto::Encryptable, date_time, encryption::Encryption, errors::{CustomResult, ValidationError}, + ext_traits::ValueExt, id_type, pii, type_name, - types::keymanager::{Identifier, KeyManagerState}, + types::keymanager::{Identifier, KeyManagerState, ToEncryptable}, }; use diesel_models::{enums, merchant_connector_account::MerchantConnectorAccountUpdateInternal}; use error_stack::ResultExt; use masking::{PeekInterface, Secret}; +#[cfg(feature = "v2")] +use router_env::logger; +use rustc_hash::FxHashMap; +use serde_json::Value; use super::behaviour; -use crate::type_encryption::{crypto_operation, AsyncLift, CryptoOperation}; +#[cfg(feature = "v2")] +use crate::errors::api_error_response::ApiErrorResponse; +use crate::{ + router_data, + type_encryption::{crypto_operation, CryptoOperation}, +}; #[cfg(feature = "v1")] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, router_derive::ToEncryption)] pub struct MerchantConnectorAccount { pub merchant_id: id_type::MerchantId, pub connector_name: String, - pub connector_account_details: Encryptable, + #[encrypt] + pub connector_account_details: Encryptable>, pub test_mode: Option, pub disabled: Option, pub merchant_connector_id: id_type::MerchantConnectorAccountId, @@ -37,8 +50,10 @@ pub struct MerchantConnectorAccount { pub applepay_verified_domains: Option>, pub pm_auth_config: Option, pub status: enums::ConnectorStatus, - pub connector_wallets_details: Option>, - pub additional_merchant_data: Option>, + #[encrypt] + pub connector_wallets_details: Option>>, + #[encrypt] + pub additional_merchant_data: Option>>, pub version: common_enums::ApiVersion, } @@ -47,17 +62,31 @@ impl MerchantConnectorAccount { pub fn get_id(&self) -> id_type::MerchantConnectorAccountId { self.merchant_connector_id.clone() } + + pub fn get_connector_account_details( + &self, + ) -> error_stack::Result + { + self.connector_account_details + .get_inner() + .clone() + .parse_value("ConnectorAuthType") + } + pub fn get_connector_test_mode(&self) -> Option { + self.test_mode + } } #[cfg(feature = "v2")] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, router_derive::ToEncryption)] pub struct MerchantConnectorAccount { pub id: id_type::MerchantConnectorAccountId, pub merchant_id: id_type::MerchantId, pub connector_name: String, - pub connector_account_details: Encryptable, + #[encrypt] + pub connector_account_details: Encryptable>, pub disabled: Option, - pub payment_methods_enabled: Option>, + pub payment_methods_enabled: Option>, pub connector_type: enums::ConnectorType, pub metadata: Option, pub frm_configs: Option>, @@ -69,8 +98,10 @@ pub struct MerchantConnectorAccount { pub applepay_verified_domains: Option>, pub pm_auth_config: Option, pub status: enums::ConnectorStatus, - pub connector_wallets_details: Option>, - pub additional_merchant_data: Option>, + #[encrypt] + pub connector_wallets_details: Option>>, + #[encrypt] + pub additional_merchant_data: Option>>, pub version: common_enums::ApiVersion, } @@ -79,6 +110,91 @@ impl MerchantConnectorAccount { pub fn get_id(&self) -> id_type::MerchantConnectorAccountId { self.id.clone() } + + pub fn get_metadata(&self) -> Option { + self.metadata.clone() + } + + pub fn is_disabled(&self) -> bool { + self.disabled.unwrap_or(false) + } + + pub fn get_connector_account_details( + &self, + ) -> error_stack::Result + { + use common_utils::ext_traits::ValueExt; + + self.connector_account_details + .get_inner() + .clone() + .parse_value("ConnectorAuthType") + } + pub fn get_connector_test_mode(&self) -> Option { + todo!() + } +} + +#[cfg(feature = "v2")] +/// Holds the payment methods enabled for a connector along with the connector name +/// This struct is a flattened representation of the payment methods enabled for a connector +#[derive(Debug)] +pub struct PaymentMethodsEnabledForConnector { + pub payment_methods_enabled: common_types::payment_methods::RequestPaymentMethodTypes, + pub payment_method: common_enums::PaymentMethod, + pub connector: String, +} + +#[cfg(feature = "v2")] +/// Holds the payment methods enabled for a connector +pub struct FlattenedPaymentMethodsEnabled { + pub payment_methods_enabled: Vec, +} + +#[cfg(feature = "v2")] +impl FlattenedPaymentMethodsEnabled { + /// This functions flattens the payment methods enabled from the connector accounts + /// Retains the connector name and payment method in every flattened element + pub fn from_payment_connectors_list(payment_connectors: Vec) -> Self { + let payment_methods_enabled_flattened_with_connector = payment_connectors + .into_iter() + .map(|connector| { + ( + connector.payment_methods_enabled.unwrap_or_default(), + connector.connector_name, + ) + }) + .flat_map(|(payment_method_enabled, connector_name)| { + payment_method_enabled + .into_iter() + .flat_map(move |payment_method| { + let request_payment_methods_enabled = + payment_method.payment_method_subtypes.unwrap_or_default(); + let length = request_payment_methods_enabled.len(); + request_payment_methods_enabled.into_iter().zip( + std::iter::repeat(( + connector_name.clone(), + payment_method.payment_method_type, + )) + .take(length), + ) + }) + }) + .map( + |(request_payment_methods, (connector_name, payment_method))| { + PaymentMethodsEnabledForConnector { + payment_methods_enabled: request_payment_methods, + connector: connector_name.clone(), + payment_method, + } + }, + ) + .collect(); + + Self { + payment_methods_enabled: payment_methods_enabled_flattened_with_connector, + } + } } #[cfg(feature = "v1")] @@ -87,20 +203,20 @@ pub enum MerchantConnectorAccountUpdate { Update { connector_type: Option, connector_name: Option, - connector_account_details: Option>, + connector_account_details: Box>>, test_mode: Option, disabled: Option, merchant_connector_id: Option, payment_methods_enabled: Option>, metadata: Option, frm_configs: Option>, - connector_webhook_details: Option, + connector_webhook_details: Box>, applepay_verified_domains: Option>, - pm_auth_config: Option, + pm_auth_config: Box>, connector_label: Option, status: Option, - connector_wallets_details: Option>, - additional_merchant_data: Option>, + connector_wallets_details: Box>>, + additional_merchant_data: Box>>, }, ConnectorWalletDetailsUpdate { connector_wallets_details: Encryptable, @@ -112,18 +228,18 @@ pub enum MerchantConnectorAccountUpdate { pub enum MerchantConnectorAccountUpdate { Update { connector_type: Option, - connector_account_details: Option>, + connector_account_details: Box>>, disabled: Option, - payment_methods_enabled: Option>, + payment_methods_enabled: Option>, metadata: Option, frm_configs: Option>, connector_webhook_details: Option, applepay_verified_domains: Option>, - pm_auth_config: Option, + pm_auth_config: Box>, connector_label: Option, status: Option, - connector_wallets_details: Option>, - additional_merchant_data: Option>, + connector_wallets_details: Box>>, + additional_merchant_data: Box>>, }, ConnectorWalletDetailsUpdate { connector_wallets_details: Encryptable, @@ -175,21 +291,34 @@ impl behaviour::Conversion for MerchantConnectorAccount { _key_manager_identifier: Identifier, ) -> CustomResult { let identifier = Identifier::Merchant(other.merchant_id.clone()); + let decrypted_data = crypto_operation( + state, + type_name!(Self::DstType), + CryptoOperation::BatchDecrypt(EncryptedMerchantConnectorAccount::to_encryptable( + EncryptedMerchantConnectorAccount { + connector_account_details: other.connector_account_details, + additional_merchant_data: other.additional_merchant_data, + connector_wallets_details: other.connector_wallets_details, + }, + )), + identifier.clone(), + key.peek(), + ) + .await + .and_then(|val| val.try_into_batchoperation()) + .change_context(ValidationError::InvalidValue { + message: "Failed while decrypting connector account details".to_string(), + })?; + + let decrypted_data = EncryptedMerchantConnectorAccount::from_encryptable(decrypted_data) + .change_context(ValidationError::InvalidValue { + message: "Failed while decrypting connector account details".to_string(), + })?; + Ok(Self { merchant_id: other.merchant_id, connector_name: other.connector_name, - connector_account_details: crypto_operation( - state, - type_name!(Self::DstType), - CryptoOperation::Decrypt(other.connector_account_details), - identifier.clone(), - key.peek(), - ) - .await - .and_then(|val| val.try_into_operation()) - .change_context(ValidationError::InvalidValue { - message: "Failed while decrypting connector account details".to_string(), - })?, + connector_account_details: decrypted_data.connector_account_details, test_mode: other.test_mode, disabled: other.disabled, merchant_connector_id: other.merchant_connector_id, @@ -213,41 +342,8 @@ impl behaviour::Conversion for MerchantConnectorAccount { applepay_verified_domains: other.applepay_verified_domains, pm_auth_config: other.pm_auth_config, status: other.status, - connector_wallets_details: other - .connector_wallets_details - .async_lift(|inner| async { - crypto_operation( - state, - type_name!(Self::DstType), - CryptoOperation::DecryptOptional(inner), - identifier.clone(), - key.peek(), - ) - .await - .and_then(|val| val.try_into_optionaloperation()) - }) - .await - .change_context(ValidationError::InvalidValue { - message: "Failed while decrypting connector wallets details".to_string(), - })?, - additional_merchant_data: if let Some(data) = other.additional_merchant_data { - Some( - crypto_operation( - state, - type_name!(Self::DstType), - CryptoOperation::Decrypt(data), - identifier, - key.peek(), - ) - .await - .and_then(|val| val.try_into_operation()) - .change_context(ValidationError::InvalidValue { - message: "Failed while decrypting additional_merchant_data".to_string(), - })?, - ) - } else { - None - }, + connector_wallets_details: decrypted_data.connector_wallets_details, + additional_merchant_data: decrypted_data.additional_merchant_data, version: other.version, }) } @@ -324,22 +420,36 @@ impl behaviour::Conversion for MerchantConnectorAccount { _key_manager_identifier: Identifier, ) -> CustomResult { let identifier = Identifier::Merchant(other.merchant_id.clone()); + + let decrypted_data = crypto_operation( + state, + type_name!(Self::DstType), + CryptoOperation::BatchDecrypt(EncryptedMerchantConnectorAccount::to_encryptable( + EncryptedMerchantConnectorAccount { + connector_account_details: other.connector_account_details, + additional_merchant_data: other.additional_merchant_data, + connector_wallets_details: other.connector_wallets_details, + }, + )), + identifier.clone(), + key.peek(), + ) + .await + .and_then(|val| val.try_into_batchoperation()) + .change_context(ValidationError::InvalidValue { + message: "Failed while decrypting connector account details".to_string(), + })?; + + let decrypted_data = EncryptedMerchantConnectorAccount::from_encryptable(decrypted_data) + .change_context(ValidationError::InvalidValue { + message: "Failed while decrypting connector account details".to_string(), + })?; + Ok(Self { id: other.id, merchant_id: other.merchant_id, connector_name: other.connector_name, - connector_account_details: crypto_operation( - state, - type_name!(Self::DstType), - CryptoOperation::Decrypt(other.connector_account_details), - identifier.clone(), - key.peek(), - ) - .await - .and_then(|val| val.try_into_operation()) - .change_context(ValidationError::InvalidValue { - message: "Failed while decrypting connector account details".to_string(), - })?, + connector_account_details: decrypted_data.connector_account_details, disabled: other.disabled, payment_methods_enabled: other.payment_methods_enabled, connector_type: other.connector_type, @@ -354,41 +464,8 @@ impl behaviour::Conversion for MerchantConnectorAccount { applepay_verified_domains: other.applepay_verified_domains, pm_auth_config: other.pm_auth_config, status: other.status, - connector_wallets_details: other - .connector_wallets_details - .async_lift(|inner| async { - crypto_operation( - state, - type_name!(Self::DstType), - CryptoOperation::DecryptOptional(inner), - identifier.clone(), - key.peek(), - ) - .await - .and_then(|val| val.try_into_optionaloperation()) - }) - .await - .change_context(ValidationError::InvalidValue { - message: "Failed while decrypting connector wallets details".to_string(), - })?, - additional_merchant_data: if let Some(data) = other.additional_merchant_data { - Some( - crypto_operation( - state, - type_name!(Self::DstType), - CryptoOperation::Decrypt(data), - identifier, - key.peek(), - ) - .await - .and_then(|val| val.try_into_operation()) - .change_context(ValidationError::InvalidValue { - message: "Failed while decrypting additional_merchant_data".to_string(), - })?, - ) - } else { - None - }, + connector_wallets_details: decrypted_data.connector_wallets_details, + additional_merchant_data: decrypted_data.additional_merchant_data, version: other.version, }) } @@ -453,9 +530,9 @@ impl From for MerchantConnectorAccountUpdateInte frm_configs: None, frm_config: frm_configs, modified_at: Some(date_time::now()), - connector_webhook_details, + connector_webhook_details: *connector_webhook_details, applepay_verified_domains, - pm_auth_config, + pm_auth_config: *pm_auth_config, connector_label, status, connector_wallets_details: connector_wallets_details.map(Encryption::from), @@ -515,7 +592,7 @@ impl From for MerchantConnectorAccountUpdateInte modified_at: Some(date_time::now()), connector_webhook_details, applepay_verified_domains, - pm_auth_config, + pm_auth_config: *pm_auth_config, connector_label, status, connector_wallets_details: connector_wallets_details.map(Encryption::from), @@ -542,3 +619,60 @@ impl From for MerchantConnectorAccountUpdateInte } } } + +common_utils::create_list_wrapper!( + MerchantConnectorAccounts, + MerchantConnectorAccount, + impl_functions: { + #[cfg(feature = "v2")] + pub fn get_connector_and_supporting_payment_method_type_for_session_call( + &self, + ) -> Vec<(&MerchantConnectorAccount, common_enums::PaymentMethodType)> { + // This vector is created to work around lifetimes + let ref_vector = Vec::default(); + + let connector_and_supporting_payment_method_type = self.iter().flat_map(|connector_account| { + connector_account + .payment_methods_enabled.as_ref() + .unwrap_or(&Vec::default()) + .iter() + .flat_map(|payment_method_types| payment_method_types.payment_method_subtypes.as_ref().unwrap_or(&ref_vector)) + .filter(|payment_method_types_enabled| { + payment_method_types_enabled.payment_experience == Some(api_models::enums::PaymentExperience::InvokeSdkClient) + }) + .map(|payment_method_types| { + (connector_account, payment_method_types.payment_method_subtype) + }) + .collect::>() + }).collect(); + connector_and_supporting_payment_method_type + } + pub fn filter_based_on_profile_and_connector_type( + self, + profile_id: &id_type::ProfileId, + connector_type: common_enums::ConnectorType, + ) -> Self { + self.into_iter() + .filter(|mca| &mca.profile_id == profile_id && mca.connector_type == connector_type) + .collect() + } + pub fn is_merchant_connector_account_id_in_connector_mandate_details( + &self, + profile_id: Option<&id_type::ProfileId>, + connector_mandate_details: &diesel_models::PaymentsMandateReference, + ) -> bool { + let mca_ids = self + .iter() + .filter(|mca| { + mca.disabled.is_some_and(|disabled| !disabled) + && profile_id.is_some_and(|profile_id| *profile_id == mca.profile_id) + }) + .map(|mca| mca.get_id()) + .collect::>(); + + connector_mandate_details + .keys() + .any(|mca_id| mca_ids.contains(mca_id)) + } + } +); diff --git a/crates/hyperswitch_domain_models/src/payment_address.rs b/crates/hyperswitch_domain_models/src/payment_address.rs index 548dd0c54166..176bef0c88bb 100644 --- a/crates/hyperswitch_domain_models/src/payment_address.rs +++ b/crates/hyperswitch_domain_models/src/payment_address.rs @@ -1,4 +1,4 @@ -use api_models::payments::Address; +use crate::address::Address; #[derive(Clone, Default, Debug)] pub struct PaymentAddress { diff --git a/crates/hyperswitch_domain_models/src/payment_method_data.rs b/crates/hyperswitch_domain_models/src/payment_method_data.rs index 9f505f267120..8c19a20ef322 100644 --- a/crates/hyperswitch_domain_models/src/payment_method_data.rs +++ b/crates/hyperswitch_domain_models/src/payment_method_data.rs @@ -1,4 +1,7 @@ -use api_models::payments::{additional_info as payment_additional_types, ExtendedCardInfo}; +use api_models::{ + mandates, payment_methods, + payments::{additional_info as payment_additional_types, ExtendedCardInfo}, +}; use common_enums::enums as api_enums; use common_utils::{ id_type, @@ -16,6 +19,7 @@ use time::Date; #[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] pub enum PaymentMethodData { Card(Card), + CardDetailsForNetworkTransactionId(CardDetailsForNetworkTransactionId), CardRedirect(CardRedirectData), Wallet(WalletData), PayLater(PayLaterData), @@ -32,6 +36,7 @@ pub enum PaymentMethodData { CardToken(CardToken), OpenBanking(OpenBankingData), NetworkToken(NetworkTokenData), + MobilePayment(MobilePaymentData), } #[derive(Debug, Clone, PartialEq, Eq)] @@ -43,7 +48,9 @@ pub enum ApplePayFlow { impl PaymentMethodData { pub fn get_payment_method(&self) -> Option { match self { - Self::Card(_) | Self::NetworkToken(_) => Some(common_enums::PaymentMethod::Card), + Self::Card(_) | Self::NetworkToken(_) | Self::CardDetailsForNetworkTransactionId(_) => { + 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), @@ -57,6 +64,7 @@ impl PaymentMethodData { Self::Voucher(_) => Some(common_enums::PaymentMethod::Voucher), Self::GiftCard(_) => Some(common_enums::PaymentMethod::GiftCard), Self::OpenBanking(_) => Some(common_enums::PaymentMethod::OpenBanking), + Self::MobilePayment(_) => Some(common_enums::PaymentMethod::MobilePayment), Self::CardToken(_) | Self::MandatePayment => None, } } @@ -74,6 +82,65 @@ pub struct Card { pub card_issuing_country: Option, pub bank_code: Option, pub nick_name: Option>, + pub card_holder_name: Option>, +} + +#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Default)] +pub struct CardDetailsForNetworkTransactionId { + pub card_number: cards::CardNumber, + pub card_exp_month: Secret, + pub card_exp_year: 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>, + pub card_holder_name: Option>, +} + +impl CardDetailsForNetworkTransactionId { + pub fn get_nti_and_card_details_for_mit_flow( + recurring_details: mandates::RecurringDetails, + ) -> Option<(api_models::payments::MandateReferenceId, Self)> { + let network_transaction_id_and_card_details = match recurring_details { + mandates::RecurringDetails::NetworkTransactionIdAndCardDetails( + network_transaction_id_and_card_details, + ) => Some(network_transaction_id_and_card_details), + mandates::RecurringDetails::MandateId(_) + | mandates::RecurringDetails::PaymentMethodId(_) + | mandates::RecurringDetails::ProcessorPaymentToken(_) => None, + }?; + + let mandate_reference_id = api_models::payments::MandateReferenceId::NetworkMandateId( + network_transaction_id_and_card_details + .network_transaction_id + .peek() + .to_string(), + ); + + Some(( + mandate_reference_id, + network_transaction_id_and_card_details.clone().into(), + )) + } +} + +impl From for CardDetailsForNetworkTransactionId { + fn from(card_details_for_nti: mandates::NetworkTransactionIdAndCardDetails) -> Self { + Self { + card_number: card_details_for_nti.card_number, + card_exp_month: card_details_for_nti.card_exp_month, + card_exp_year: card_details_for_nti.card_exp_year, + card_issuer: card_details_for_nti.card_issuer, + card_network: card_details_for_nti.card_network, + card_type: card_details_for_nti.card_type, + card_issuing_country: card_details_for_nti.card_issuing_country, + bank_code: card_details_for_nti.bank_code, + nick_name: card_details_for_nti.nick_name, + card_holder_name: card_details_for_nti.card_holder_name, + } + } } #[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] @@ -88,6 +155,7 @@ pub enum CardRedirectData { pub enum PayLaterData { KlarnaRedirect {}, KlarnaSdk { token: String }, + KlarnaCheckout {}, AffirmRedirect {}, AfterpayClearpayRedirect {}, PayBrightRedirect {}, @@ -97,7 +165,6 @@ pub enum PayLaterData { } #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] - pub enum WalletData { AliPayQr(Box), AliPayRedirect(AliPayRedirection), @@ -117,6 +184,7 @@ pub enum WalletData { MobilePayRedirect(Box), PaypalRedirect(PaypalRedirection), PaypalSdk(PayPalWalletData), + Paze(PazeWalletData), SamsungPay(Box), TwintRedirect {}, VippsRedirect {}, @@ -134,6 +202,12 @@ pub struct MifinityData { pub language_preference: Option, } +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "snake_case")] +pub struct PazeWalletData { + pub complete_response: Secret, +} + #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] #[serde(rename_all = "snake_case")] pub struct SamsungPayWalletData { @@ -145,7 +219,8 @@ pub struct SamsungPayWalletData { pub struct SamsungPayWalletCredentials { pub method: Option, pub recurring_payment: Option, - pub card_brand: String, + pub card_brand: common_enums::SamsungPayCardBrand, + pub dpan_last_four_digits: Option, #[serde(rename = "card_last4digits")] pub card_last_four_digits: String, #[serde(rename = "3_d_s")] @@ -162,7 +237,6 @@ pub struct SamsungPayTokenData { } #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)] - pub struct GooglePayWalletData { /// The type of payment method pub pm_type: String, @@ -232,7 +306,6 @@ pub struct MobilePayRedirection {} 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, @@ -289,7 +362,6 @@ pub struct ApplepayPaymentMethod { } #[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] - pub enum RealTimePaymentData { DuitNow {}, Fps {}, @@ -298,7 +370,6 @@ pub enum RealTimePaymentData { } #[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] - pub enum BankRedirectData { BancontactCard { card_number: Option, @@ -526,6 +597,18 @@ pub struct NetworkTokenData { pub card_issuing_country: Option, pub bank_code: Option, pub nick_name: Option>, + pub eci: Option, +} + +#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum MobilePaymentData { + DirectCarrierBilling { + /// The phone number of the user + msisdn: String, + /// Unique user identifier + client_uid: Option, + }, } impl From for PaymentMethodData { @@ -575,6 +658,9 @@ impl From for PaymentMethodData { api_models::payments::PaymentMethodData::OpenBanking(ob_data) => { Self::OpenBanking(From::from(ob_data)) } + api_models::payments::PaymentMethodData::MobilePayment(mobile_payment_data) => { + Self::MobilePayment(From::from(mobile_payment_data)) + } } } } @@ -585,7 +671,7 @@ impl From for Card { card_number, card_exp_month, card_exp_year, - card_holder_name: _, + card_holder_name, card_cvc, card_issuer, card_network, @@ -606,6 +692,7 @@ impl From for Card { card_issuing_country, bank_code, nick_name, + card_holder_name, } } } @@ -689,6 +776,9 @@ impl From for WalletData { token: paypal_sdk_data.token, }) } + api_models::payments::WalletData::Paze(paze_data) => { + Self::Paze(PazeWalletData::from(paze_data)) + } api_models::payments::WalletData::SamsungPay(samsung_pay_data) => { Self::SamsungPay(Box::new(SamsungPayWalletData::from(samsung_pay_data))) } @@ -754,18 +844,49 @@ impl From for ApplePayWalletData { } } +impl From for SamsungPayTokenData { + fn from(samsung_pay_token_data: api_models::payments::SamsungPayTokenData) -> Self { + Self { + three_ds_type: samsung_pay_token_data.three_ds_type, + version: samsung_pay_token_data.version, + data: samsung_pay_token_data.data, + } + } +} + +impl From for PazeWalletData { + fn from(value: api_models::payments::PazeWalletData) -> Self { + Self { + complete_response: value.complete_response, + } + } +} + impl From> for SamsungPayWalletData { fn from(value: Box) -> Self { - Self { - payment_credential: SamsungPayWalletCredentials { - method: value.payment_credential.method, - recurring_payment: value.payment_credential.recurring_payment, - card_brand: value.payment_credential.card_brand, - card_last_four_digits: value.payment_credential.card_last_four_digits, - token_data: SamsungPayTokenData { - three_ds_type: value.payment_credential.token_data.three_ds_type, - version: value.payment_credential.token_data.version, - data: value.payment_credential.token_data.data, + match value.payment_credential { + api_models::payments::SamsungPayWalletCredentials::SamsungPayWalletDataForApp( + samsung_pay_app_wallet_data, + ) => Self { + payment_credential: SamsungPayWalletCredentials { + method: samsung_pay_app_wallet_data.method, + recurring_payment: samsung_pay_app_wallet_data.recurring_payment, + card_brand: samsung_pay_app_wallet_data.payment_card_brand.into(), + dpan_last_four_digits: samsung_pay_app_wallet_data.payment_last4_dpan, + card_last_four_digits: samsung_pay_app_wallet_data.payment_last4_fpan, + token_data: samsung_pay_app_wallet_data.token_data.into(), + }, + }, + api_models::payments::SamsungPayWalletCredentials::SamsungPayWalletDataForWeb( + samsung_pay_web_wallet_data, + ) => Self { + payment_credential: SamsungPayWalletCredentials { + method: samsung_pay_web_wallet_data.method, + recurring_payment: samsung_pay_web_wallet_data.recurring_payment, + card_brand: samsung_pay_web_wallet_data.card_brand.into(), + dpan_last_four_digits: None, + card_last_four_digits: samsung_pay_web_wallet_data.card_last_four_digits, + token_data: samsung_pay_web_wallet_data.token_data.into(), }, }, } @@ -777,6 +898,7 @@ impl From for PayLaterData { match value { api_models::payments::PayLaterData::KlarnaRedirect { .. } => Self::KlarnaRedirect {}, api_models::payments::PayLaterData::KlarnaSdk { token } => Self::KlarnaSdk { token }, + api_models::payments::PayLaterData::KlarnaCheckout {} => Self::KlarnaCheckout {}, api_models::payments::PayLaterData::AffirmRedirect {} => Self::AffirmRedirect {}, api_models::payments::PayLaterData::AfterpayClearpayRedirect { .. } => { Self::AfterpayClearpayRedirect {} @@ -1293,6 +1415,27 @@ impl From for api_models::payments::OpenBankingData { } } +impl From for MobilePaymentData { + fn from(value: api_models::payments::MobilePaymentData) -> Self { + match value { + api_models::payments::MobilePaymentData::DirectCarrierBilling { + msisdn, + client_uid, + } => Self::DirectCarrierBilling { msisdn, client_uid }, + } + } +} + +impl From for api_models::payments::MobilePaymentData { + fn from(value: MobilePaymentData) -> Self { + match value { + MobilePaymentData::DirectCarrierBilling { msisdn, client_uid } => { + Self::DirectCarrierBilling { msisdn, client_uid } + } + } + } +} + #[derive(Debug, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct TokenizedCardValue1 { @@ -1302,6 +1445,7 @@ pub struct TokenizedCardValue1 { pub nickname: Option, pub card_last_four: Option, pub card_token: Option, + pub card_holder_name: Option>, } #[derive(Debug, serde::Serialize, serde::Deserialize)] @@ -1388,6 +1532,7 @@ impl GetPaymentMethodType for WalletData { Self::MbWayRedirect(_) => api_enums::PaymentMethodType::MbWay, Self::MobilePayRedirect(_) => api_enums::PaymentMethodType::MobilePay, Self::PaypalRedirect(_) | Self::PaypalSdk(_) => api_enums::PaymentMethodType::Paypal, + Self::Paze(_) => api_enums::PaymentMethodType::Paze, Self::SamsungPay(_) => api_enums::PaymentMethodType::SamsungPay, Self::TwintRedirect {} => api_enums::PaymentMethodType::Twint, Self::VippsRedirect {} => api_enums::PaymentMethodType::Vipps, @@ -1407,6 +1552,7 @@ impl GetPaymentMethodType for PayLaterData { match self { Self::KlarnaRedirect { .. } => api_enums::PaymentMethodType::Klarna, Self::KlarnaSdk { .. } => api_enums::PaymentMethodType::Klarna, + Self::KlarnaCheckout {} => api_enums::PaymentMethodType::Klarna, Self::AffirmRedirect {} => api_enums::PaymentMethodType::Affirm, Self::AfterpayClearpayRedirect { .. } => api_enums::PaymentMethodType::AfterpayClearpay, Self::PayBrightRedirect {} => api_enums::PaymentMethodType::PayBright, @@ -1542,6 +1688,14 @@ impl GetPaymentMethodType for OpenBankingData { } } +impl GetPaymentMethodType for MobilePaymentData { + fn get_payment_method_type(&self) -> api_enums::PaymentMethodType { + match self { + Self::DirectCarrierBilling { .. } => api_enums::PaymentMethodType::DirectCarrierBilling, + } + } +} + impl From for ExtendedCardInfo { fn from(value: Card) -> Self { Self { @@ -1558,3 +1712,13 @@ impl From for ExtendedCardInfo { } } } + +impl From for payment_methods::PaymentMethodDataWalletInfo { + fn from(item: GooglePayWalletData) -> Self { + Self { + last4: item.info.card_details, + card_network: item.info.card_network, + card_type: item.pm_type, + } + } +} diff --git a/crates/hyperswitch_domain_models/src/payment_methods.rs b/crates/hyperswitch_domain_models/src/payment_methods.rs index af3c657c34ad..d03762dd64e8 100644 --- a/crates/hyperswitch_domain_models/src/payment_methods.rs +++ b/crates/hyperswitch_domain_models/src/payment_methods.rs @@ -1,10 +1,9 @@ +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +use common_utils::crypto::Encryptable; use common_utils::{ crypto::OptionalEncryptableValue, - // date_time, - // encryption::Encryption, errors::{CustomResult, ValidationError}, - pii, - type_name, + pii, type_name, types::keymanager, }; use diesel_models::enums as storage_enums; @@ -12,8 +11,24 @@ use error_stack::ResultExt; use masking::{PeekInterface, Secret}; use time::PrimitiveDateTime; +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +use crate::type_encryption::OptionalEncryptableJsonType; use crate::type_encryption::{crypto_operation, AsyncLift, CryptoOperation}; +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +pub struct VaultId(String); + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl VaultId { + pub fn get_string_repr(&self) -> &String { + &self.0 + } + + pub fn generate(id: String) -> Self { + Self(id) + } +} #[cfg(all( any(feature = "v1", feature = "v2"), not(feature = "payment_methods_v2") @@ -59,17 +74,17 @@ pub struct PaymentMethod { #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[derive(Clone, Debug)] pub struct PaymentMethod { - pub customer_id: common_utils::id_type::CustomerId, + pub customer_id: common_utils::id_type::GlobalCustomerId, pub merchant_id: common_utils::id_type::MerchantId, pub created_at: PrimitiveDateTime, pub last_modified: PrimitiveDateTime, - pub payment_method: Option, - pub payment_method_type: Option, - pub metadata: Option, - pub payment_method_data: OptionalEncryptableValue, - pub locker_id: Option, + pub payment_method_type: Option, + pub payment_method_subtype: Option, + pub payment_method_data: + OptionalEncryptableJsonType, + pub locker_id: Option, pub last_used_at: PrimitiveDateTime, - pub connector_mandate_details: Option, + pub connector_mandate_details: Option, pub customer_acceptance: Option, pub status: storage_enums::PaymentMethodStatus, pub network_transaction_id: Option, @@ -97,6 +112,32 @@ impl PaymentMethod { pub fn get_id(&self) -> &common_utils::id_type::GlobalPaymentMethodId { &self.id } + + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") + ))] + pub fn get_payment_method_type(&self) -> Option { + self.payment_method + } + + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] + pub fn get_payment_method_type(&self) -> Option { + self.payment_method_type + } + + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") + ))] + pub fn get_payment_method_subtype(&self) -> Option { + self.payment_method_type + } + + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] + pub fn get_payment_method_subtype(&self) -> Option { + self.payment_method_subtype + } } #[cfg(all( @@ -298,11 +339,10 @@ impl super::behaviour::Conversion for PaymentMethod { id: self.id, created_at: self.created_at, last_modified: self.last_modified, - payment_method: self.payment_method, - payment_method_type: self.payment_method_type, - metadata: self.metadata, + payment_method_type_v2: self.payment_method_type, + payment_method_subtype: self.payment_method_subtype, payment_method_data: self.payment_method_data.map(|val| val.into()), - locker_id: self.locker_id, + locker_id: self.locker_id.map(|id| id.get_string_repr().clone()), last_used_at: self.last_used_at, connector_mandate_details: self.connector_mandate_details, customer_acceptance: self.customer_acceptance, @@ -339,9 +379,8 @@ impl super::behaviour::Conversion for PaymentMethod { id: item.id, created_at: item.created_at, last_modified: item.last_modified, - payment_method: item.payment_method, - payment_method_type: item.payment_method_type, - metadata: item.metadata, + payment_method_type: item.payment_method_type_v2, + payment_method_subtype: item.payment_method_subtype, payment_method_data: item .payment_method_data .async_lift(|inner| async { @@ -356,7 +395,7 @@ impl super::behaviour::Conversion for PaymentMethod { .and_then(|val| val.try_into_optionaloperation()) }) .await?, - locker_id: item.locker_id, + locker_id: item.locker_id.map(VaultId::generate), last_used_at: item.last_used_at, connector_mandate_details: item.connector_mandate_details, customer_acceptance: item.customer_acceptance, @@ -411,11 +450,10 @@ impl super::behaviour::Conversion for PaymentMethod { id: self.id, created_at: self.created_at, last_modified: self.last_modified, - payment_method: self.payment_method, - payment_method_type: self.payment_method_type, - metadata: self.metadata, + payment_method_type_v2: self.payment_method_type, + payment_method_subtype: self.payment_method_subtype, payment_method_data: self.payment_method_data.map(|val| val.into()), - locker_id: self.locker_id, + locker_id: self.locker_id.map(|id| id.get_string_repr().clone()), last_used_at: self.last_used_at, connector_mandate_details: self.connector_mandate_details, customer_acceptance: self.customer_acceptance, diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index 2d7d49e81c7d..095b4c33a46e 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -1,21 +1,50 @@ #[cfg(feature = "v2")] use std::marker::PhantomData; -use common_utils::{self, crypto::Encryptable, id_type, pii, types::MinorUnit}; +#[cfg(feature = "v2")] +use api_models::payments::SessionToken; +#[cfg(feature = "v2")] +use common_utils::ext_traits::ValueExt; +use common_utils::{ + self, + crypto::Encryptable, + encryption::Encryption, + errors::CustomResult, + id_type, pii, + types::{keymanager::ToEncryptable, MinorUnit}, +}; use diesel_models::payment_intent::TaxDetails; +#[cfg(feature = "v2")] +use error_stack::ResultExt; use masking::Secret; +#[cfg(feature = "v2")] +use payment_intent::PaymentIntentUpdate; +use router_derive::ToEncryption; +use rustc_hash::FxHashMap; +use serde_json::Value; use time::PrimitiveDateTime; pub mod payment_attempt; pub mod payment_intent; use common_enums as storage_enums; +#[cfg(feature = "v2")] +use diesel_models::{ + ephemeral_key, + types::{FeatureMetadata, OrderDetailsWithAmount}, +}; use self::payment_attempt::PaymentAttempt; +#[cfg(feature = "v1")] use crate::RemoteStorageObject; +#[cfg(feature = "v2")] +use crate::{ + address::Address, business_profile, errors, merchant_account, payment_address, + payment_method_data, ApiModelToDieselModelConvertor, +}; #[cfg(feature = "v1")] -#[derive(Clone, Debug, PartialEq, serde::Serialize)] +#[derive(Clone, Debug, PartialEq, serde::Serialize, ToEncryption)] pub struct PaymentIntent { pub payment_id: id_type::PaymentId, pub merchant_id: id_type::MerchantId, @@ -27,7 +56,7 @@ pub struct PaymentIntent { pub customer_id: Option, pub description: Option, pub return_url: Option, - pub metadata: Option, + pub metadata: Option, pub connector_id: Option, pub shipping_address_id: Option, pub billing_address_id: Option, @@ -46,9 +75,9 @@ pub struct PaymentIntent { pub business_country: Option, pub business_label: Option, pub order_details: Option>, - pub allowed_payment_method_types: Option, - pub connector_metadata: Option, - pub feature_metadata: Option, + pub allowed_payment_method_types: Option, + pub connector_metadata: Option, + pub feature_metadata: Option, pub attempt_count: i16, pub profile_id: Option, pub payment_link_id: Option, @@ -66,16 +95,21 @@ 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 split_payments: Option, pub frm_metadata: Option, - pub customer_details: Option>>, - pub billing_details: Option>>, + #[encrypt] + pub customer_details: Option>>, + #[encrypt] + pub billing_details: Option>>, pub merchant_order_reference_id: Option, - pub shipping_details: Option>>, + #[encrypt] + pub shipping_details: Option>>, pub is_payment_processor_token_flow: Option, pub organization_id: id_type::OrganizationId, pub tax_details: Option, pub skip_external_tax_calculation: Option, + pub psd2_sca_exemption_type: Option, + pub platform_merchant_id: Option, } impl PaymentIntent { @@ -88,43 +122,42 @@ impl PaymentIntent { pub fn get_id(&self) -> &id_type::GlobalPaymentId { &self.id } -} -#[cfg(feature = "v2")] -#[derive(Clone, Debug, PartialEq, serde::Serialize)] -pub enum TaxCalculationOverride { - /// Skip calling the external tax provider - Skip, - /// Calculate tax by calling the external tax provider - Calculate, -} - -#[cfg(feature = "v2")] -#[derive(Clone, Debug, PartialEq, serde::Serialize)] -pub enum SurchargeCalculationOverride { - /// Skip calculating surcharge - Skip, - /// Calculate surcharge - Calculate, -} - -#[cfg(feature = "v2")] -impl From> for TaxCalculationOverride { - fn from(value: Option) -> Self { - match value { - Some(true) => Self::Calculate, - _ => Self::Skip, - } + #[cfg(feature = "v2")] + /// This is the url to which the customer will be redirected to, to complete the redirection flow + pub fn create_start_redirection_url( + &self, + base_url: &str, + publishable_key: String, + ) -> CustomResult { + let start_redirection_url = &format!( + "{}/v2/payments/{}/start-redirection?publishable_key={}&profile_id={}", + base_url, + self.get_id().get_string_repr(), + publishable_key, + self.profile_id.get_string_repr() + ); + url::Url::parse(start_redirection_url) + .change_context(errors::api_error_response::ApiErrorResponse::InternalServerError) + .attach_printable("Error creating start redirection url") } -} -#[cfg(feature = "v2")] -impl From> for SurchargeCalculationOverride { - fn from(value: Option) -> Self { - match value { - Some(true) => Self::Calculate, - _ => Self::Skip, - } + #[cfg(feature = "v2")] + /// This is the url to which the customer will be redirected to, after completing the redirection flow + pub fn create_finish_redirection_url( + &self, + base_url: &str, + publishable_key: &str, + ) -> CustomResult { + let finish_redirection_url = format!( + "{base_url}/v2/payments/{}/finish-redirection/{publishable_key}/{}", + self.id.get_string_repr(), + self.profile_id.get_string_repr() + ); + + url::Url::parse(&finish_redirection_url) + .change_context(errors::api_error_response::ApiErrorResponse::InternalServerError) + .attach_printable("Error creating finish redirection url") } } @@ -140,36 +173,105 @@ pub struct AmountDetails { /// Tax details related to the order. This will be calculated by the external tax provider pub tax_details: Option, /// The action to whether calculate tax by calling external tax provider or not - pub skip_external_tax_calculation: TaxCalculationOverride, + pub skip_external_tax_calculation: common_enums::TaxCalculationOverride, /// The action to whether calculate surcharge or not - pub skip_surcharge_calculation: SurchargeCalculationOverride, + pub skip_surcharge_calculation: common_enums::SurchargeCalculationOverride, /// The surcharge amount to be added to the order, collected from the merchant pub surcharge_amount: Option, /// tax on surcharge amount pub tax_on_surcharge: Option, + /// The total amount captured for the order. This is the sum of all the captured amounts for the order. + /// For automatic captures, this will be the same as net amount for the order + pub amount_captured: Option, } #[cfg(feature = "v2")] impl AmountDetails { /// Get the action to whether calculate surcharge or not as a boolean value fn get_surcharge_action_as_bool(&self) -> bool { - match self.skip_surcharge_calculation { - SurchargeCalculationOverride::Skip => false, - SurchargeCalculationOverride::Calculate => true, - } + self.skip_surcharge_calculation.as_bool() } /// Get the action to whether calculate external tax or not as a boolean value fn get_external_tax_action_as_bool(&self) -> bool { - match self.skip_external_tax_calculation { - TaxCalculationOverride::Skip => false, - TaxCalculationOverride::Calculate => true, + self.skip_external_tax_calculation.as_bool() + } + + /// Calculate the net amount for the order + pub fn calculate_net_amount(&self) -> MinorUnit { + self.order_amount + + self.shipping_cost.unwrap_or(MinorUnit::zero()) + + self.surcharge_amount.unwrap_or(MinorUnit::zero()) + + self.tax_on_surcharge.unwrap_or(MinorUnit::zero()) + } + + pub fn create_attempt_amount_details( + &self, + confirm_intent_request: &api_models::payments::PaymentsConfirmIntentRequest, + ) -> payment_attempt::AttemptAmountDetails { + let net_amount = self.calculate_net_amount(); + + let surcharge_amount = match self.skip_surcharge_calculation { + common_enums::SurchargeCalculationOverride::Skip => self.surcharge_amount, + common_enums::SurchargeCalculationOverride::Calculate => None, + }; + + let tax_on_surcharge = match self.skip_surcharge_calculation { + common_enums::SurchargeCalculationOverride::Skip => self.tax_on_surcharge, + common_enums::SurchargeCalculationOverride::Calculate => None, + }; + + let order_tax_amount = match self.skip_external_tax_calculation { + common_enums::TaxCalculationOverride::Skip => { + self.tax_details.as_ref().and_then(|tax_details| { + tax_details.get_tax_amount(confirm_intent_request.payment_method_subtype) + }) + } + common_enums::TaxCalculationOverride::Calculate => None, + }; + + payment_attempt::AttemptAmountDetails::from(payment_attempt::AttemptAmountDetailsSetter { + net_amount, + amount_to_capture: None, + surcharge_amount, + tax_on_surcharge, + // This will be updated when we receive response from the connector + amount_capturable: MinorUnit::zero(), + shipping_cost: self.shipping_cost, + order_tax_amount, + }) + } + + pub fn update_from_request(self, req: &api_models::payments::AmountDetailsUpdate) -> Self { + Self { + order_amount: req + .order_amount() + .unwrap_or(self.order_amount.into()) + .into(), + currency: req.currency().unwrap_or(self.currency), + shipping_cost: req.shipping_cost().or(self.shipping_cost), + tax_details: req + .order_tax_amount() + .map(|order_tax_amount| TaxDetails { + default: Some(diesel_models::DefaultTax { order_tax_amount }), + payment_method_type: None, + }) + .or(self.tax_details), + skip_external_tax_calculation: req + .skip_external_tax_calculation() + .unwrap_or(self.skip_external_tax_calculation), + skip_surcharge_calculation: req + .skip_surcharge_calculation() + .unwrap_or(self.skip_surcharge_calculation), + surcharge_amount: req.surcharge_amount().or(self.surcharge_amount), + tax_on_surcharge: req.tax_on_surcharge().or(self.tax_on_surcharge), + amount_captured: self.amount_captured, } } } #[cfg(feature = "v2")] -#[derive(Clone, Debug, PartialEq, serde::Serialize)] +#[derive(Clone, Debug, PartialEq, serde::Serialize, ToEncryption)] pub struct PaymentIntent { /// The global identifier for the payment intent. This is generated by the system. /// The format of the global id is `{cell_id:5}_pay_{time_ordered_uuid:32}`. @@ -183,7 +285,7 @@ pub struct PaymentIntent { /// The total amount captured for the order. This is the sum of all the captured amounts for the order. pub amount_captured: Option, /// The identifier for the customer. This is the identifier for the customer in the merchant's system. - pub customer_id: Option, + pub customer_id: Option, /// The description of the order. This will be passed to connectors which support description. pub description: Option, /// The return url for the payment. This is the url to which the user will be redirected after the payment is completed. @@ -200,19 +302,19 @@ pub struct PaymentIntent { pub modified_at: PrimitiveDateTime, #[serde(with = "common_utils::custom_serde::iso8601::option")] pub last_synced: Option, - pub setup_future_usage: Option, + pub setup_future_usage: storage_enums::FutureUsage, /// The client secret that is generated for the payment. This is used to authenticate the payment from client facing apis. pub client_secret: common_utils::types::ClientSecret, /// The active attempt for the payment intent. This is the payment attempt that is currently active for the payment intent. - pub active_attempt: RemoteStorageObject, + pub active_attempt_id: Option, /// The order details for the payment. - pub order_details: Option>, + pub order_details: Option>>, /// This is the list of payment method types that are allowed for the payment intent. /// This field allows the merchant to restrict the payment methods that can be used for the payment intent. - pub allowed_payment_method_types: Option, + pub allowed_payment_method_types: Option>, /// This metadata contains details about pub connector_metadata: Option, - pub feature_metadata: Option, + pub feature_metadata: Option, /// Number of attempts that have been made for the order pub attempt_count: i16, /// The profile id for the payment. @@ -225,30 +327,33 @@ pub struct PaymentIntent { /// Denotes the last instance which updated the payment pub updated_by: String, /// Denotes whether merchant requested for incremental authorization to be enabled for this payment. - pub request_incremental_authorization: Option, + pub request_incremental_authorization: storage_enums::RequestIncrementalAuthorization, /// Denotes the number of authorizations that have been made for the payment. pub authorization_count: Option, /// Denotes the client secret expiry for the payment. This is the time at which the client secret will expire. - #[serde(with = "common_utils::custom_serde::iso8601::option")] - pub session_expiry: Option, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub session_expiry: PrimitiveDateTime, /// Denotes whether merchant requested for 3ds authentication to be enabled for this payment. pub request_external_three_ds_authentication: common_enums::External3dsAuthenticationRequest, /// Metadata related to fraud and risk management pub frm_metadata: Option, /// The details of the customer in a denormalized form. Only a subset of fields are stored. - pub customer_details: Option>>, + #[encrypt] + pub customer_details: Option>>, /// The reference id for the order in the merchant's system. This value can be passed by the merchant. - pub merchant_reference_id: Option, + pub merchant_reference_id: Option, /// The billing address for the order in a denormalized form. - pub billing_address: Option>>, + #[encrypt(ty = Value)] + pub billing_address: Option>, /// The shipping address for the order in a denormalized form. - pub shipping_address: Option>>, + #[encrypt(ty = Value)] + pub shipping_address: Option>, /// Capture method for the payment - pub capture_method: Option, + pub capture_method: storage_enums::CaptureMethod, /// Authentication type that is requested by the merchant for this payment. - pub authentication_type: Option, + pub authentication_type: common_enums::AuthenticationType, /// This contains the pre routing results that are done when routing is done during listing the payment methods. - pub prerouting_algorithm: Option, + pub prerouting_algorithm: Option, /// The organization id for the payment. This is derived from the merchant account pub organization_id: id_type::OrganizationId, /// Denotes the request by the merchant whether to enable a payment link for this payment. @@ -261,6 +366,206 @@ pub struct PaymentIntent { pub payment_link_config: Option, /// The straight through routing algorithm id that is used for this payment. This overrides the default routing algorithm that is configured in business profile. pub routing_algorithm_id: Option, + /// Identifier for the platform merchant. + pub platform_merchant_id: Option, +} + +#[cfg(feature = "v2")] +impl PaymentIntent { + fn get_request_incremental_authorization_value( + request: &api_models::payments::PaymentsCreateIntentRequest, + ) -> CustomResult< + common_enums::RequestIncrementalAuthorization, + errors::api_error_response::ApiErrorResponse, + > { + request.request_incremental_authorization + .map(|request_incremental_authorization| { + if request_incremental_authorization == common_enums::RequestIncrementalAuthorization::True { + if request.capture_method == Some(common_enums::CaptureMethod::Automatic) { + Err(errors::api_error_response::ApiErrorResponse::InvalidRequestData { message: "incremental authorization is not supported when capture_method is automatic".to_owned() })? + } + Ok(common_enums::RequestIncrementalAuthorization::True) + } else { + Ok(common_enums::RequestIncrementalAuthorization::False) + } + }) + .unwrap_or(Ok(common_enums::RequestIncrementalAuthorization::default())) + } + + /// Check if the client secret is associated with the payment and if it has been expired + pub fn validate_client_secret( + &self, + client_secret: &common_utils::types::ClientSecret, + ) -> Result<(), errors::api_error_response::ApiErrorResponse> { + common_utils::fp_utils::when(self.client_secret != *client_secret, || { + Err(errors::api_error_response::ApiErrorResponse::ClientSecretInvalid) + })?; + + common_utils::fp_utils::when(self.session_expiry < common_utils::date_time::now(), || { + Err(errors::api_error_response::ApiErrorResponse::ClientSecretExpired) + })?; + + Ok(()) + } + + pub async fn create_domain_model_from_request( + payment_id: &id_type::GlobalPaymentId, + merchant_account: &merchant_account::MerchantAccount, + profile: &business_profile::Profile, + request: api_models::payments::PaymentsCreateIntentRequest, + decrypted_payment_intent: DecryptedPaymentIntent, + platform_merchant_id: Option<&merchant_account::MerchantAccount>, + ) -> CustomResult { + let connector_metadata = request + .get_connector_metadata_as_value() + .change_context(errors::api_error_response::ApiErrorResponse::InternalServerError) + .attach_printable("Error getting connector metadata as value")?; + let request_incremental_authorization = + Self::get_request_incremental_authorization_value(&request)?; + let allowed_payment_method_types = request.allowed_payment_method_types; + + let session_expiry = + common_utils::date_time::now().saturating_add(time::Duration::seconds( + request.session_expiry.map(i64::from).unwrap_or( + profile + .session_expiry + .unwrap_or(common_utils::consts::DEFAULT_SESSION_EXPIRY), + ), + )); + let client_secret = payment_id.generate_client_secret(); + let order_details = request.order_details.map(|order_details| { + order_details + .into_iter() + .map(|order_detail| Secret::new(OrderDetailsWithAmount::convert_from(order_detail))) + .collect() + }); + Ok(Self { + id: payment_id.clone(), + merchant_id: merchant_account.get_id().clone(), + // Intent status would be RequiresPaymentMethod because we are creating a new payment intent + status: common_enums::IntentStatus::RequiresPaymentMethod, + amount_details: AmountDetails::from(request.amount_details), + amount_captured: None, + customer_id: request.customer_id, + description: request.description, + return_url: request.return_url, + metadata: request.metadata, + statement_descriptor: request.statement_descriptor, + created_at: common_utils::date_time::now(), + modified_at: common_utils::date_time::now(), + last_synced: None, + setup_future_usage: request.setup_future_usage.unwrap_or_default(), + client_secret, + active_attempt_id: None, + order_details, + allowed_payment_method_types, + connector_metadata, + feature_metadata: request.feature_metadata.map(FeatureMetadata::convert_from), + // Attempt count is 0 in create intent as no attempt is made yet + attempt_count: 0, + profile_id: profile.get_id().clone(), + payment_link_id: None, + frm_merchant_decision: None, + updated_by: merchant_account.storage_scheme.to_string(), + request_incremental_authorization, + // Authorization count is 0 in create intent as no authorization is made yet + authorization_count: Some(0), + session_expiry, + request_external_three_ds_authentication: request + .request_external_three_ds_authentication + .unwrap_or_default(), + frm_metadata: request.frm_metadata, + customer_details: None, + merchant_reference_id: request.merchant_reference_id, + billing_address: decrypted_payment_intent + .billing_address + .as_ref() + .map(|data| { + data.clone() + .deserialize_inner_value(|value| value.parse_value("Address")) + }) + .transpose() + .change_context(errors::api_error_response::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to decode billing address")?, + shipping_address: decrypted_payment_intent + .shipping_address + .as_ref() + .map(|data| { + data.clone() + .deserialize_inner_value(|value| value.parse_value("Address")) + }) + .transpose() + .change_context(errors::api_error_response::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to decode shipping address")?, + capture_method: request.capture_method.unwrap_or_default(), + authentication_type: request.authentication_type.unwrap_or_default(), + prerouting_algorithm: None, + organization_id: merchant_account.organization_id.clone(), + enable_payment_link: request.payment_link_enabled.unwrap_or_default(), + apply_mit_exemption: request.apply_mit_exemption.unwrap_or_default(), + customer_present: request.customer_present.unwrap_or_default(), + payment_link_config: request + .payment_link_config + .map(ApiModelToDieselModelConvertor::convert_from), + routing_algorithm_id: request.routing_algorithm_id, + platform_merchant_id: platform_merchant_id + .map(|merchant_account| merchant_account.get_id().to_owned()), + }) + } +} + +#[cfg(feature = "v1")] +#[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, + pub browser_name: Option, + pub x_client_platform: Option, + pub x_merchant_domain: Option, + pub locale: Option, + pub x_app_id: Option, + pub x_redirect_uri: Option, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct ClickToPayMetaData { + pub dpa_id: String, + pub dpa_name: String, + pub locale: String, + pub card_brands: Vec, + pub acquirer_bin: String, + pub acquirer_merchant_id: String, + pub merchant_category_code: String, + pub merchant_country_code: String, +} + +// TODO: uncomment fields as necessary +#[cfg(feature = "v2")] +#[derive(Default, Debug, Clone)] +pub struct HeaderPayload { + /// The source with which the payment is confirmed. + pub payment_confirm_source: Option, + // pub client_source: Option, + // pub client_version: Option, + pub x_hs_latency: Option, + pub browser_name: Option, + pub x_client_platform: Option, + pub x_merchant_domain: Option, + pub locale: Option, + pub x_app_id: Option, + pub x_redirect_uri: Option, + pub client_secret: Option, +} + +impl HeaderPayload { + pub fn with_source(payment_confirm_source: common_enums::PaymentSource) -> Self { + Self { + payment_confirm_source: Some(payment_confirm_source), + ..Default::default() + } + } } #[cfg(feature = "v2")] @@ -271,4 +576,55 @@ where { pub flow: PhantomData, pub payment_intent: PaymentIntent, + pub sessions_token: Vec, +} + +// TODO: Check if this can be merged with existing payment data +#[cfg(feature = "v2")] +#[derive(Clone)] +pub struct PaymentConfirmData +where + F: Clone, +{ + pub flow: PhantomData, + pub payment_intent: PaymentIntent, + pub payment_attempt: PaymentAttempt, + pub payment_method_data: Option, + pub payment_address: payment_address::PaymentAddress, +} + +#[cfg(feature = "v2")] +#[derive(Clone)] +pub struct PaymentStatusData +where + F: Clone, +{ + pub flow: PhantomData, + pub payment_intent: PaymentIntent, + pub payment_attempt: Option, + pub payment_address: payment_address::PaymentAddress, + /// Should the payment status be synced with connector + /// This will depend on the payment status and the force sync flag in the request + pub should_sync_with_connector: bool, +} + +#[cfg(feature = "v2")] +#[derive(Clone)] +pub struct PaymentCaptureData +where + F: Clone, +{ + pub flow: PhantomData, + pub payment_intent: PaymentIntent, + pub payment_attempt: PaymentAttempt, +} + +#[cfg(feature = "v2")] +impl PaymentStatusData +where + F: Clone, +{ + pub fn get_payment_id(&self) -> &id_type::GlobalPaymentId { + &self.payment_intent.id + } } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index c71e08c79e8e..0a7ada39ecdb 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -1,30 +1,45 @@ #[cfg(all(feature = "v1", feature = "olap"))] use api_models::enums::Connector; use common_enums as storage_enums; +#[cfg(feature = "v2")] +use common_utils::{ + crypto::Encryptable, encryption::Encryption, ext_traits::ValueExt, + types::keymanager::ToEncryptable, +}; use common_utils::{ errors::{CustomResult, ValidationError}, id_type, pii, types::{ keymanager::{self, KeyManagerState}, - MinorUnit, + ConnectorTransactionId, ConnectorTransactionIdTrait, MinorUnit, }, }; use diesel_models::{ - PaymentAttempt as DieselPaymentAttempt, PaymentAttemptNew as DieselPaymentAttemptNew, + ConnectorMandateReferenceId, PaymentAttempt as DieselPaymentAttempt, + PaymentAttemptNew as DieselPaymentAttemptNew, + PaymentAttemptUpdate as DieselPaymentAttemptUpdate, }; use error_stack::ResultExt; +#[cfg(feature = "v2")] +use masking::PeekInterface; use masking::Secret; +#[cfg(feature = "v2")] +use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; +#[cfg(feature = "v2")] +use serde_json::Value; use time::PrimitiveDateTime; #[cfg(all(feature = "v1", feature = "olap"))] use super::PaymentIntent; #[cfg(feature = "v2")] -use crate::merchant_key_store::MerchantKeyStore; +use crate::type_encryption::{crypto_operation, CryptoOperation}; +#[cfg(feature = "v2")] +use crate::{address::Address, merchant_key_store::MerchantKeyStore, router_response_types}; use crate::{ behaviour, errors, mandates::{MandateDataType, MandateDetails}, - ForeignIDRef, + router_request_types, ForeignIDRef, }; #[async_trait::async_trait] @@ -54,7 +69,7 @@ pub trait PaymentAttemptInterface { ) -> error_stack::Result; #[cfg(feature = "v2")] - async fn update_payment_attempt_with_attempt_id( + async fn update_payment_attempt( &self, key_manager_state: &KeyManagerState, merchant_key_store: &MerchantKeyStore, @@ -66,7 +81,7 @@ pub trait PaymentAttemptInterface { #[cfg(feature = "v1")] async fn find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( &self, - connector_transaction_id: &str, + connector_transaction_id: &ConnectorTransactionId, payment_id: &id_type::PaymentId, merchant_id: &id_type::MerchantId, storage_scheme: storage_enums::MerchantStorageScheme, @@ -96,6 +111,16 @@ pub trait PaymentAttemptInterface { storage_scheme: storage_enums::MerchantStorageScheme, ) -> error_stack::Result; + #[cfg(feature = "v2")] + async fn find_payment_attempt_by_profile_id_connector_transaction_id( + &self, + key_manager_state: &KeyManagerState, + merchant_key_store: &MerchantKeyStore, + profile_id: &id_type::ProfileId, + connector_transaction_id: &str, + _storage_scheme: storage_enums::MerchantStorageScheme, + ) -> CustomResult; + #[cfg(feature = "v1")] async fn find_payment_attempt_by_payment_id_merchant_id_attempt_id( &self, @@ -118,7 +143,7 @@ pub trait PaymentAttemptInterface { &self, key_manager_state: &KeyManagerState, merchant_key_store: &MerchantKeyStore, - attempt_id: &str, + attempt_id: &id_type::GlobalAttemptId, storage_scheme: storage_enums::MerchantStorageScheme, ) -> error_stack::Result; @@ -157,71 +182,226 @@ pub trait PaymentAttemptInterface { payment_method_type: Option>, authentication_type: Option>, merchant_connector_id: Option>, - profile_id_list: Option>, + card_network: Option>, storage_scheme: storage_enums::MerchantStorageScheme, ) -> error_stack::Result; } +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] +pub struct AttemptAmountDetails { + /// The total amount for this payment attempt. This includes all the surcharge and tax amounts. + net_amount: MinorUnit, + /// The amount that has to be captured, + amount_to_capture: Option, + /// Surcharge amount for the payment attempt. + /// This is either derived by surcharge rules, or sent by the merchant + surcharge_amount: Option, + /// Tax amount for the payment attempt + /// This is either derived by surcharge rules, or sent by the merchant + tax_on_surcharge: Option, + /// The total amount that can be captured for this payment attempt. + amount_capturable: MinorUnit, + /// Shipping cost for the payment attempt. + shipping_cost: Option, + /// Tax amount for the order. + /// This is either derived by calling an external tax processor, or sent by the merchant + order_tax_amount: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] +pub struct AttemptAmountDetailsSetter { + /// The total amount for this payment attempt. This includes all the surcharge and tax amounts. + pub net_amount: MinorUnit, + /// The amount that has to be captured, + pub amount_to_capture: Option, + /// Surcharge amount for the payment attempt. + /// This is either derived by surcharge rules, or sent by the merchant + pub surcharge_amount: Option, + /// Tax amount for the payment attempt + /// This is either derived by surcharge rules, or sent by the merchant + pub tax_on_surcharge: Option, + /// The total amount that can be captured for this payment attempt. + pub amount_capturable: MinorUnit, + /// Shipping cost for the payment attempt. + pub shipping_cost: Option, + /// Tax amount for the order. + /// This is either derived by calling an external tax processor, or sent by the merchant + pub order_tax_amount: Option, +} + +/// Set the fields of amount details, since the fields are not public +impl From for AttemptAmountDetails { + fn from(setter: AttemptAmountDetailsSetter) -> Self { + Self { + net_amount: setter.net_amount, + amount_to_capture: setter.amount_to_capture, + surcharge_amount: setter.surcharge_amount, + tax_on_surcharge: setter.tax_on_surcharge, + amount_capturable: setter.amount_capturable, + shipping_cost: setter.shipping_cost, + order_tax_amount: setter.order_tax_amount, + } + } +} + +impl AttemptAmountDetails { + pub fn get_net_amount(&self) -> MinorUnit { + self.net_amount + } + + pub fn get_amount_to_capture(&self) -> Option { + self.amount_to_capture + } + + pub fn get_surcharge_amount(&self) -> Option { + self.surcharge_amount + } + + pub fn get_tax_on_surcharge(&self) -> Option { + self.tax_on_surcharge + } + + pub fn get_amount_capturable(&self) -> MinorUnit { + self.amount_capturable + } + + pub fn get_shipping_cost(&self) -> Option { + self.shipping_cost + } + + pub fn get_order_tax_amount(&self) -> Option { + self.order_tax_amount + } + + pub fn set_amount_to_capture(&mut self, amount_to_capture: MinorUnit) { + self.amount_to_capture = Some(amount_to_capture); + } + + /// Validate the amount to capture that is sent in the request + pub fn validate_amount_to_capture( + &self, + request_amount_to_capture: MinorUnit, + ) -> Result<(), ValidationError> { + common_utils::fp_utils::when(request_amount_to_capture > self.get_net_amount(), || { + Err(ValidationError::IncorrectValueProvided { + field_name: "amount_to_capture", + }) + }) + } +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] +pub struct ErrorDetails { + /// The error code that was returned by the connector. + /// This is a mandatory field. This is used to lookup the global status map record for unified code and retries + pub code: String, + /// The error message that was returned by the connector. + /// This is a mandatory field. This is used to lookup the global status map record for unified message and retries + pub message: String, + /// The detailed error reason that was returned by the connector. + pub reason: Option, + /// The unified code that is generated by the application based on the global status map record. + /// This can be relied upon for common error code across all connectors + pub unified_code: Option, + /// The unified message that is generated by the application based on the global status map record. + /// This can be relied upon for common error code across all connectors + /// If there is translation available, message will be translated to the requested language + pub unified_message: Option, +} + +/// Domain model for the payment attempt. +/// Few fields which are related are grouped together for better readability and understandability. +/// These fields will be flattened and stored in the database in individual columns #[cfg(feature = "v2")] -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, serde::Serialize, router_derive::ToEncryption)] pub struct PaymentAttempt { + /// Payment id for the payment attempt pub payment_id: id_type::GlobalPaymentId, + /// Merchant id for the payment attempt pub merchant_id: id_type::MerchantId, + /// Amount details for the payment attempt + pub amount_details: AttemptAmountDetails, + /// Status of the payment attempt. This is the status that is updated by the connector. + /// The intent status is updated by the AttemptStatus. pub status: storage_enums::AttemptStatus, - pub net_amount: MinorUnit, + /// Name of the connector that was used for the payment attempt. The connector is either decided by + /// either running the routing algorithm or by straight through processing request. + /// This will be updated before calling the connector + // TODO: use connector enum, this should be done in v1 as well as a part of moving to domain types wherever possible pub connector: Option, - pub amount_to_capture: Option, - pub error_message: Option, - pub surcharge_amount: Option, - pub tax_on_surcharge: Option, - pub confirm: bool, - pub authentication_type: Option, - #[serde(with = "common_utils::custom_serde::iso8601")] + /// Error details in case the payment attempt failed + pub error: Option, + /// The authentication type that was requested for the payment attempt. + /// This authentication type maybe decided by step up 3ds or by running the decision engine. + pub authentication_type: storage_enums::AuthenticationType, + /// The time at which the payment attempt was created pub created_at: PrimitiveDateTime, - #[serde(with = "common_utils::custom_serde::iso8601")] + /// The time at which the payment attempt was last modified pub modified_at: PrimitiveDateTime, - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] pub last_synced: Option, + /// The reason for the cancellation of the payment attempt. Some connectors will have strict rules regarding the values this can have + /// Cancellation reason will be validated at the connector level when building the request pub cancellation_reason: Option, - pub browser_info: Option, - pub error_code: Option, + /// Browser information required for 3DS authentication + pub browser_info: Option, + /// Payment token is the token used for temporary use in case the payment method is stored in vault pub payment_token: Option, - pub connector_metadata: Option, + /// Metadata that is returned by the connector. + pub connector_metadata: Option, pub payment_experience: Option, - pub payment_method_data: Option, - pub routing_result: Option, + /// The insensitive data of the payment method data is stored here + pub payment_method_data: Option, + /// The result of the routing algorithm. + /// This will store the list of connectors and other related information that was used to route the payment. + // TODO: change this to type instead of serde_json::Value + pub routing_result: Option, pub preprocessing_step_id: Option, - pub error_reason: Option, + /// Number of captures that have happened for the payment attempt pub multiple_capture_count: Option, - // reference to the payment at connector side + /// A reference to the payment at connector side. This is returned by the connector pub connector_response_reference_id: Option, - pub amount_capturable: MinorUnit, + /// Whether the payment was updated by postgres or redis pub updated_by: String, - pub authentication_data: Option, - pub encoded_data: Option, + /// The authentication data which is used for external authentication + pub redirection_data: Option, + pub encoded_data: Option>, pub merchant_connector_id: Option, - pub unified_code: Option, - pub unified_message: Option, + /// Whether external 3DS authentication was attempted for this payment. + /// This is based on the configuration of the merchant in the business profile pub external_three_ds_authentication_attempted: Option, + /// The connector that was used for external authentication pub authentication_connector: Option, + /// The foreign key reference to the authentication details pub authentication_id: Option, - pub payment_method_billing_address_id: Option, pub fingerprint_id: Option, pub charge_id: Option, pub client_source: Option, pub client_version: Option, + // TODO: use a type here instead of value pub customer_acceptance: Option, + /// The profile id for the payment attempt. This will be derived from payment intent. pub profile_id: id_type::ProfileId, + /// The organization id for the payment attempt. This will be derived from payment intent. pub organization_id: id_type::OrganizationId, - pub payment_method_type: Option, - pub payment_method_id: Option, + /// Payment method type for the payment attempt + pub payment_method_type: storage_enums::PaymentMethod, + /// Foreig key reference of Payment method id in case the payment instrument was stored + pub payment_method_id: Option, + /// The reference to the payment at the connector side pub connector_payment_id: Option, - pub payment_method_subtype: Option, + /// The payment method subtype for the payment attempt. + pub payment_method_subtype: storage_enums::PaymentMethodType, + /// The authentication type that was applied for the payment attempt. pub authentication_applied: Option, + /// A reference to the payment at connector side. This is returned by the connector pub external_reference_id: Option, - pub shipping_cost: Option, - pub order_tax_amount: Option, - pub id: String, + /// The billing address for the payment method + #[encrypt(ty = Value)] + pub payment_method_billing_address: Option>, + /// The global identifier for the payment attempt + pub id: id_type::GlobalAttemptId, + /// The connector mandate details which are stored temporarily + pub connector_mandate_detail: Option, } impl PaymentAttempt { @@ -232,7 +412,8 @@ impl PaymentAttempt { #[cfg(feature = "v2")] pub fn get_payment_method(&self) -> Option { - self.payment_method_type + // TODO: check if we can fix this + Some(self.payment_method_type) } #[cfg(feature = "v1")] @@ -242,7 +423,8 @@ impl PaymentAttempt { #[cfg(feature = "v2")] pub fn get_payment_method_type(&self) -> Option { - self.payment_method_subtype + // TODO: check if we can fix this + Some(self.payment_method_subtype) } #[cfg(feature = "v1")] @@ -251,7 +433,7 @@ impl PaymentAttempt { } #[cfg(feature = "v2")] - pub fn get_id(&self) -> &str { + pub fn get_id(&self) -> &id_type::GlobalAttemptId { &self.id } @@ -264,6 +446,82 @@ impl PaymentAttempt { pub fn get_connector_payment_id(&self) -> Option<&str> { self.connector_payment_id.as_deref() } + + /// Construct the domain model from the ConfirmIntentRequest and PaymentIntent + #[cfg(feature = "v2")] + pub async fn create_domain_model( + payment_intent: &super::PaymentIntent, + cell_id: id_type::CellId, + storage_scheme: storage_enums::MerchantStorageScheme, + request: &api_models::payments::PaymentsConfirmIntentRequest, + encrypted_data: DecryptedPaymentAttempt, + ) -> CustomResult { + let id = id_type::GlobalAttemptId::generate(&cell_id); + let intent_amount_details = payment_intent.amount_details.clone(); + + let attempt_amount_details = intent_amount_details.create_attempt_amount_details(request); + + let now = common_utils::date_time::now(); + + let payment_method_billing_address = encrypted_data + .payment_method_billing_address + .as_ref() + .map(|data| { + data.clone() + .deserialize_inner_value(|value| value.parse_value("Address")) + }) + .transpose() + .change_context(errors::api_error_response::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to decode billing address")?; + + Ok(Self { + payment_id: payment_intent.id.clone(), + merchant_id: payment_intent.merchant_id.clone(), + amount_details: attempt_amount_details, + status: common_enums::AttemptStatus::Started, + // This will be decided by the routing algorithm and updated in update trackers + // right before calling the connector + connector: None, + authentication_type: payment_intent.authentication_type, + created_at: now, + modified_at: now, + last_synced: None, + cancellation_reason: None, + browser_info: request.browser_info.clone(), + payment_token: None, + connector_metadata: None, + payment_experience: None, + payment_method_data: None, + routing_result: None, + preprocessing_step_id: None, + multiple_capture_count: None, + connector_response_reference_id: None, + updated_by: storage_scheme.to_string(), + redirection_data: None, + encoded_data: None, + merchant_connector_id: None, + external_three_ds_authentication_attempted: None, + authentication_connector: None, + authentication_id: None, + fingerprint_id: None, + charge_id: None, + client_source: None, + client_version: None, + customer_acceptance: None, + profile_id: payment_intent.profile_id.clone(), + organization_id: payment_intent.organization_id.clone(), + payment_method_type: request.payment_method_type, + payment_method_id: None, + connector_payment_id: None, + payment_method_subtype: request.payment_method_subtype, + authentication_applied: None, + external_reference_id: None, + payment_method_billing_address, + error: None, + connector_mandate_detail: None, + id, + }) + } } #[cfg(feature = "v1")] @@ -273,15 +531,12 @@ pub struct PaymentAttempt { pub merchant_id: id_type::MerchantId, pub attempt_id: String, pub status: storage_enums::AttemptStatus, - pub amount: MinorUnit, - pub net_amount: MinorUnit, + pub net_amount: NetAmount, 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 payment_method_id: Option, pub payment_method: Option, pub connector_transaction_id: Option, @@ -334,12 +589,157 @@ pub struct PaymentAttempt { pub customer_acceptance: Option, pub profile_id: id_type::ProfileId, pub organization_id: id_type::OrganizationId, - pub shipping_cost: Option, - pub order_tax_amount: Option, + pub connector_mandate_detail: Option, +} + +#[cfg(feature = "v1")] +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Default)] +pub struct NetAmount { + /// The payment amount + order_amount: MinorUnit, + /// The shipping cost of the order + shipping_cost: Option, + /// Tax amount related to the order + order_tax_amount: Option, + /// The surcharge amount to be added to the order + surcharge_amount: Option, + /// tax on surcharge amount + tax_on_surcharge: Option, +} + +#[cfg(feature = "v1")] +impl NetAmount { + pub fn new( + order_amount: MinorUnit, + shipping_cost: Option, + order_tax_amount: Option, + surcharge_amount: Option, + tax_on_surcharge: Option, + ) -> Self { + Self { + order_amount, + shipping_cost, + order_tax_amount, + surcharge_amount, + tax_on_surcharge, + } + } + + pub fn get_order_amount(&self) -> MinorUnit { + self.order_amount + } + + pub fn get_shipping_cost(&self) -> Option { + self.shipping_cost + } + + pub fn get_order_tax_amount(&self) -> Option { + self.order_tax_amount + } + + pub fn get_surcharge_amount(&self) -> Option { + self.surcharge_amount + } + + pub fn get_tax_on_surcharge(&self) -> Option { + self.tax_on_surcharge + } + + pub fn get_total_surcharge_amount(&self) -> Option { + self.surcharge_amount + .map(|surcharge_amount| surcharge_amount + self.tax_on_surcharge.unwrap_or_default()) + } + + pub fn get_total_amount(&self) -> MinorUnit { + self.order_amount + + self.shipping_cost.unwrap_or_default() + + self.order_tax_amount.unwrap_or_default() + + self.surcharge_amount.unwrap_or_default() + + self.tax_on_surcharge.unwrap_or_default() + } + + pub fn set_order_amount(&mut self, order_amount: MinorUnit) { + self.order_amount = order_amount; + } + + pub fn set_order_tax_amount(&mut self, order_tax_amount: Option) { + self.order_tax_amount = order_tax_amount; + } + + pub fn set_surcharge_details( + &mut self, + surcharge_details: Option, + ) { + self.surcharge_amount = surcharge_details + .clone() + .map(|details| details.surcharge_amount); + self.tax_on_surcharge = surcharge_details.map(|details| details.tax_on_surcharge_amount); + } + + pub fn from_payments_request( + payments_request: &api_models::payments::PaymentsRequest, + order_amount: MinorUnit, + ) -> Self { + let surcharge_amount = payments_request + .surcharge_details + .map(|surcharge_details| surcharge_details.surcharge_amount); + let tax_on_surcharge = payments_request + .surcharge_details + .and_then(|surcharge_details| surcharge_details.tax_amount); + Self { + order_amount, + shipping_cost: payments_request.shipping_cost, + order_tax_amount: None, + surcharge_amount, + tax_on_surcharge, + } + } + + #[cfg(feature = "v1")] + pub fn from_payments_request_and_payment_attempt( + payments_request: &api_models::payments::PaymentsRequest, + payment_attempt: Option<&PaymentAttempt>, + ) -> Option { + let option_order_amount = payments_request + .amount + .map(MinorUnit::from) + .or(payment_attempt + .map(|payment_attempt| payment_attempt.net_amount.get_order_amount())); + option_order_amount.map(|order_amount| { + let shipping_cost = payments_request.shipping_cost.or(payment_attempt + .and_then(|payment_attempt| payment_attempt.net_amount.get_shipping_cost())); + let order_tax_amount = payment_attempt + .and_then(|payment_attempt| payment_attempt.net_amount.get_order_tax_amount()); + let surcharge_amount = payments_request + .surcharge_details + .map(|surcharge_details| surcharge_details.get_surcharge_amount()) + .or_else(|| { + payment_attempt.and_then(|payment_attempt| { + payment_attempt.net_amount.get_surcharge_amount() + }) + }); + let tax_on_surcharge = payments_request + .surcharge_details + .and_then(|surcharge_details| surcharge_details.get_tax_amount()) + .or_else(|| { + payment_attempt.and_then(|payment_attempt| { + payment_attempt.net_amount.get_tax_on_surcharge() + }) + }); + Self { + order_amount, + shipping_cost, + order_tax_amount, + surcharge_amount, + tax_on_surcharge, + } + }) + } } #[cfg(feature = "v2")] impl PaymentAttempt { + #[track_caller] pub fn get_total_amount(&self) -> MinorUnit { todo!(); } @@ -352,14 +752,11 @@ impl PaymentAttempt { #[cfg(feature = "v1")] impl PaymentAttempt { pub fn get_total_amount(&self) -> MinorUnit { - self.amount - + self.surcharge_amount.unwrap_or_default() - + self.tax_amount.unwrap_or_default() + self.net_amount.get_total_amount() } pub fn get_total_surcharge_amount(&self) -> Option { - self.surcharge_amount - .map(|surcharge_amount| surcharge_amount + self.tax_amount.unwrap_or_default()) + self.net_amount.get_total_surcharge_amount() } } @@ -373,55 +770,6 @@ pub struct PaymentListFilters { pub authentication_type: Vec, } -#[cfg(feature = "v2")] -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct PaymentAttemptNew { - pub payment_id: id_type::PaymentId, - pub merchant_id: id_type::MerchantId, - pub status: storage_enums::AttemptStatus, - pub error_message: Option, - pub surcharge_amount: Option, - pub tax_amount: Option, - pub payment_method_id: Option, - pub confirm: bool, - pub authentication_type: Option, - pub created_at: PrimitiveDateTime, - pub modified_at: PrimitiveDateTime, - pub last_synced: Option, - pub cancellation_reason: Option, - pub browser_info: Option, - pub payment_token: Option, - pub error_code: Option, - pub connector_metadata: Option, - pub payment_experience: Option, - pub payment_method_data: Option, - pub straight_through_algorithm: Option, - pub preprocessing_step_id: Option, - pub error_reason: Option, - pub connector_response_reference_id: Option, - pub multiple_capture_count: Option, - pub amount_capturable: MinorUnit, - pub updated_by: String, - pub merchant_connector_id: Option, - pub authentication_data: Option, - pub encoded_data: Option, - pub unified_code: Option, - pub unified_message: Option, - pub net_amount: Option, - pub external_three_ds_authentication_attempted: Option, - pub authentication_connector: Option, - pub authentication_id: Option, - pub fingerprint_id: Option, - pub payment_method_billing_address_id: Option, - pub charge_id: Option, - pub client_source: Option, - pub client_version: Option, - pub customer_acceptance: Option, - pub profile_id: id_type::ProfileId, - pub organization_id: id_type::OrganizationId, - pub card_network: Option, -} - #[cfg(feature = "v1")] #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PaymentAttemptNew { @@ -429,18 +777,15 @@ pub struct PaymentAttemptNew { pub merchant_id: id_type::MerchantId, pub attempt_id: String, pub status: storage_enums::AttemptStatus, - pub amount: MinorUnit, /// amount + surcharge_amount + tax_amount /// This field will always be derived before updating in the Database - pub net_amount: MinorUnit, + pub net_amount: NetAmount, 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 payment_method_id: Option, pub payment_method: Option, pub capture_method: Option, @@ -490,45 +835,14 @@ pub struct PaymentAttemptNew { pub customer_acceptance: Option, pub profile_id: id_type::ProfileId, pub organization_id: id_type::OrganizationId, - pub shipping_cost: Option, - pub order_tax_amount: Option, -} - -#[cfg(feature = "v2")] -impl PaymentAttemptNew { - /// returns amount + surcharge_amount + tax_amount - pub fn calculate_net_amount(&self) -> MinorUnit { - todo!(); - } - - pub fn populate_derived_fields(self) -> Self { - todo!() - } -} - -#[cfg(feature = "v1")] -impl PaymentAttemptNew { - /// returns amount + surcharge_amount + tax_amount - pub fn calculate_net_amount(&self) -> MinorUnit { - self.amount - + self.surcharge_amount.unwrap_or_default() - + self.tax_amount.unwrap_or_default() - + self.shipping_cost.unwrap_or_default() - + self.order_tax_amount.unwrap_or_default() - } - - pub fn populate_derived_fields(self) -> Self { - let mut payment_attempt_new = self; - payment_attempt_new.net_amount = payment_attempt_new.calculate_net_amount(); - payment_attempt_new - } + pub connector_mandate_detail: Option, } #[cfg(feature = "v1")] #[derive(Debug, Clone, Serialize, Deserialize)] pub enum PaymentAttemptUpdate { Update { - amount: MinorUnit, + net_amount: NetAmount, currency: storage_enums::Currency, status: storage_enums::AttemptStatus, authentication_type: Option, @@ -540,8 +854,6 @@ pub enum PaymentAttemptUpdate { business_sub_label: Option, amount_to_capture: Option, capture_method: Option, - surcharge_amount: Option, - tax_amount: Option, fingerprint_id: Option, payment_method_billing_address_id: Option, updated_by: String, @@ -561,7 +873,7 @@ pub enum PaymentAttemptUpdate { updated_by: String, }, ConfirmUpdate { - amount: MinorUnit, + net_amount: NetAmount, currency: storage_enums::Currency, status: storage_enums::AttemptStatus, authentication_type: Option, @@ -579,8 +891,6 @@ pub enum PaymentAttemptUpdate { error_message: Option>, amount_capturable: Option, updated_by: String, - surcharge_amount: Option, - tax_amount: Option, merchant_connector_id: Option, external_three_ds_authentication_attempted: Option, authentication_connector: Option, @@ -591,8 +901,7 @@ pub enum PaymentAttemptUpdate { client_source: Option, client_version: Option, customer_acceptance: Option, - shipping_cost: Option, - order_tax_amount: Option, + connector_mandate_detail: Option, }, RejectUpdate { status: storage_enums::AttemptStatus, @@ -610,6 +919,10 @@ pub enum PaymentAttemptUpdate { payment_method_id: Option, updated_by: String, }, + ConnectorMandateDetailUpdate { + connector_mandate_detail: Option, + updated_by: String, + }, VoidUpdate { status: storage_enums::AttemptStatus, cancellation_reason: Option, @@ -636,6 +949,7 @@ pub enum PaymentAttemptUpdate { unified_message: Option>, payment_method_data: Option, charge_id: Option, + connector_mandate_detail: Option, }, UnresolvedResponseUpdate { status: storage_enums::AttemptStatus, @@ -694,7 +1008,7 @@ pub enum PaymentAttemptUpdate { updated_by: String, }, IncrementalAuthorizationAmountUpdate { - amount: MinorUnit, + net_amount: NetAmount, amount_capturable: MinorUnit, }, AuthenticationUpdate { @@ -714,12 +1028,427 @@ pub enum PaymentAttemptUpdate { unified_message: Option, connector_transaction_id: Option, }, + PostSessionTokensUpdate { + updated_by: String, + connector_metadata: Option, + }, +} + +#[cfg(feature = "v1")] +impl PaymentAttemptUpdate { + pub fn to_storage_model(self) -> diesel_models::PaymentAttemptUpdate { + match self { + Self::Update { + net_amount, + currency, + status, + authentication_type, + payment_method, + payment_token, + payment_method_data, + payment_method_type, + payment_experience, + business_sub_label, + amount_to_capture, + capture_method, + fingerprint_id, + payment_method_billing_address_id, + updated_by, + } => DieselPaymentAttemptUpdate::Update { + amount: net_amount.get_order_amount(), + currency, + status, + authentication_type, + payment_method, + payment_token, + payment_method_data, + payment_method_type, + payment_experience, + business_sub_label, + amount_to_capture, + capture_method, + surcharge_amount: net_amount.get_surcharge_amount(), + tax_amount: net_amount.get_tax_on_surcharge(), + fingerprint_id, + payment_method_billing_address_id, + updated_by, + }, + Self::UpdateTrackers { + payment_token, + connector, + straight_through_algorithm, + amount_capturable, + updated_by, + surcharge_amount, + tax_amount, + merchant_connector_id, + } => DieselPaymentAttemptUpdate::UpdateTrackers { + payment_token, + connector, + straight_through_algorithm, + amount_capturable, + surcharge_amount, + tax_amount, + updated_by, + merchant_connector_id, + }, + Self::AuthenticationTypeUpdate { + authentication_type, + updated_by, + } => DieselPaymentAttemptUpdate::AuthenticationTypeUpdate { + authentication_type, + updated_by, + }, + Self::BlocklistUpdate { + status, + error_code, + error_message, + updated_by, + } => DieselPaymentAttemptUpdate::BlocklistUpdate { + status, + error_code, + error_message, + updated_by, + }, + Self::ConnectorMandateDetailUpdate { + connector_mandate_detail, + updated_by, + } => DieselPaymentAttemptUpdate::ConnectorMandateDetailUpdate { + connector_mandate_detail, + updated_by, + }, + Self::PaymentMethodDetailsUpdate { + payment_method_id, + updated_by, + } => DieselPaymentAttemptUpdate::PaymentMethodDetailsUpdate { + payment_method_id, + updated_by, + }, + Self::ConfirmUpdate { + net_amount, + currency, + status, + authentication_type, + capture_method, + payment_method, + browser_info, + connector, + payment_token, + payment_method_data, + payment_method_type, + payment_experience, + business_sub_label, + straight_through_algorithm, + error_code, + error_message, + amount_capturable, + fingerprint_id, + updated_by, + merchant_connector_id: connector_id, + payment_method_id, + external_three_ds_authentication_attempted, + authentication_connector, + authentication_id, + payment_method_billing_address_id, + client_source, + client_version, + customer_acceptance, + connector_mandate_detail, + } => DieselPaymentAttemptUpdate::ConfirmUpdate { + amount: net_amount.get_order_amount(), + currency, + status, + authentication_type, + capture_method, + payment_method, + browser_info, + connector, + payment_token, + payment_method_data, + payment_method_type, + payment_experience, + business_sub_label, + straight_through_algorithm, + error_code, + error_message, + amount_capturable, + surcharge_amount: net_amount.get_surcharge_amount(), + tax_amount: net_amount.get_tax_on_surcharge(), + fingerprint_id, + updated_by, + merchant_connector_id: connector_id, + payment_method_id, + external_three_ds_authentication_attempted, + authentication_connector, + authentication_id, + payment_method_billing_address_id, + client_source, + client_version, + customer_acceptance, + shipping_cost: net_amount.get_shipping_cost(), + order_tax_amount: net_amount.get_order_tax_amount(), + connector_mandate_detail, + }, + Self::VoidUpdate { + status, + cancellation_reason, + updated_by, + } => DieselPaymentAttemptUpdate::VoidUpdate { + status, + cancellation_reason, + updated_by, + }, + Self::ResponseUpdate { + status, + connector, + connector_transaction_id, + authentication_type, + payment_method_id, + mandate_id, + connector_metadata, + payment_token, + error_code, + error_message, + error_reason, + connector_response_reference_id, + amount_capturable, + updated_by, + authentication_data, + encoded_data, + unified_code, + unified_message, + payment_method_data, + charge_id, + connector_mandate_detail, + } => DieselPaymentAttemptUpdate::ResponseUpdate { + status, + connector, + connector_transaction_id, + authentication_type, + payment_method_id, + mandate_id, + connector_metadata, + payment_token, + error_code, + error_message, + error_reason, + connector_response_reference_id, + amount_capturable, + updated_by, + authentication_data, + encoded_data, + unified_code, + unified_message, + payment_method_data, + charge_id, + connector_mandate_detail, + }, + Self::UnresolvedResponseUpdate { + status, + connector, + connector_transaction_id, + payment_method_id, + error_code, + error_message, + error_reason, + connector_response_reference_id, + updated_by, + } => DieselPaymentAttemptUpdate::UnresolvedResponseUpdate { + status, + connector, + connector_transaction_id, + payment_method_id, + error_code, + error_message, + error_reason, + connector_response_reference_id, + updated_by, + }, + Self::StatusUpdate { status, updated_by } => { + DieselPaymentAttemptUpdate::StatusUpdate { status, updated_by } + } + Self::ErrorUpdate { + connector, + status, + error_code, + error_message, + error_reason, + amount_capturable, + updated_by, + unified_code, + unified_message, + connector_transaction_id, + payment_method_data, + authentication_type, + } => DieselPaymentAttemptUpdate::ErrorUpdate { + connector, + status, + error_code, + error_message, + error_reason, + amount_capturable, + updated_by, + unified_code, + unified_message, + connector_transaction_id, + payment_method_data, + authentication_type, + }, + Self::CaptureUpdate { + multiple_capture_count, + updated_by, + amount_to_capture, + } => DieselPaymentAttemptUpdate::CaptureUpdate { + multiple_capture_count, + updated_by, + amount_to_capture, + }, + Self::PreprocessingUpdate { + status, + payment_method_id, + connector_metadata, + preprocessing_step_id, + connector_transaction_id, + connector_response_reference_id, + updated_by, + } => DieselPaymentAttemptUpdate::PreprocessingUpdate { + status, + payment_method_id, + connector_metadata, + preprocessing_step_id, + connector_transaction_id, + connector_response_reference_id, + updated_by, + }, + Self::RejectUpdate { + status, + error_code, + error_message, + updated_by, + } => DieselPaymentAttemptUpdate::RejectUpdate { + status, + error_code, + error_message, + updated_by, + }, + Self::AmountToCaptureUpdate { + status, + amount_capturable, + updated_by, + } => DieselPaymentAttemptUpdate::AmountToCaptureUpdate { + status, + amount_capturable, + updated_by, + }, + Self::ConnectorResponse { + authentication_data, + 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 { + net_amount, + amount_capturable, + } => DieselPaymentAttemptUpdate::IncrementalAuthorizationAmountUpdate { + amount: net_amount.get_order_amount(), + amount_capturable, + }, + Self::AuthenticationUpdate { + status, + external_three_ds_authentication_attempted, + authentication_connector, + authentication_id, + updated_by, + } => DieselPaymentAttemptUpdate::AuthenticationUpdate { + status, + external_three_ds_authentication_attempted, + authentication_connector, + authentication_id, + updated_by, + }, + Self::ManualUpdate { + status, + error_code, + error_message, + error_reason, + updated_by, + unified_code, + unified_message, + connector_transaction_id, + } => DieselPaymentAttemptUpdate::ManualUpdate { + status, + error_code, + error_message, + error_reason, + updated_by, + unified_code, + unified_message, + connector_transaction_id, + }, + Self::PostSessionTokensUpdate { + updated_by, + connector_metadata, + } => DieselPaymentAttemptUpdate::PostSessionTokensUpdate { + updated_by, + connector_metadata, + }, + } + } } -// TODO: Add fields as necessary #[cfg(feature = "v2")] -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum PaymentAttemptUpdate {} +#[derive(Debug, Clone, Serialize)] +pub enum PaymentAttemptUpdate { + /// Update the payment attempt on confirming the intent, before calling the connector + ConfirmIntent { + status: storage_enums::AttemptStatus, + updated_by: String, + connector: String, + merchant_connector_id: id_type::MerchantConnectorAccountId, + }, + /// Update the payment attempt on confirming the intent, after calling the connector on success response + ConfirmIntentResponse { + status: storage_enums::AttemptStatus, + connector_payment_id: Option, + updated_by: String, + redirection_data: Option, + connector_metadata: Option, + amount_capturable: Option, + }, + /// Update the payment attempt after force syncing with the connector + SyncUpdate { + status: storage_enums::AttemptStatus, + amount_capturable: Option, + updated_by: String, + }, + PreCaptureUpdate { + amount_to_capture: Option, + updated_by: String, + }, + /// Update the payment after attempting capture with the connector + CaptureUpdate { + status: storage_enums::AttemptStatus, + amount_capturable: Option, + updated_by: String, + }, + /// Update the payment attempt on confirming the intent, after calling the connector on error response + ErrorUpdate { + status: storage_enums::AttemptStatus, + amount_capturable: Option, + error: ErrorDetails, + updated_by: String, + connector_payment_id: Option, + }, +} #[cfg(feature = "v2")] impl ForeignIDRef for PaymentAttempt { @@ -751,22 +1480,27 @@ impl behaviour::Conversion for PaymentAttempt { .and_then(|card| card.get("card_network")) .and_then(|network| network.as_str()) .map(|network| network.to_string()); + let (connector_transaction_id, connector_transaction_data) = self + .connector_transaction_id + .map(ConnectorTransactionId::form_id_and_data) + .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) + .unwrap_or((None, None)); Ok(DieselPaymentAttempt { payment_id: self.payment_id, merchant_id: self.merchant_id, attempt_id: self.attempt_id, status: self.status, - amount: self.amount, + amount: self.net_amount.get_order_amount(), 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, + surcharge_amount: self.net_amount.get_surcharge_amount(), + tax_amount: self.net_amount.get_tax_on_surcharge(), payment_method_id: self.payment_method_id, payment_method: self.payment_method, - connector_transaction_id: self.connector_transaction_id, + connector_transaction_id, capture_method: self.capture_method, capture_on: self.capture_on, confirm: self.confirm, @@ -798,7 +1532,7 @@ impl behaviour::Conversion for PaymentAttempt { encoded_data: self.encoded_data, unified_code: self.unified_code, unified_message: self.unified_message, - net_amount: Some(self.net_amount), + net_amount: Some(self.net_amount.get_total_amount()), external_three_ds_authentication_attempted: self .external_three_ds_authentication_attempted, authentication_connector: self.authentication_connector, @@ -813,8 +1547,10 @@ impl behaviour::Conversion for PaymentAttempt { profile_id: self.profile_id, organization_id: self.organization_id, card_network, - order_tax_amount: self.order_tax_amount, - shipping_cost: self.shipping_cost, + connector_transaction_data, + order_tax_amount: self.net_amount.get_order_tax_amount(), + shipping_cost: self.net_amount.get_shipping_cost(), + connector_mandate_detail: self.connector_mandate_detail, }) } @@ -828,24 +1564,29 @@ impl behaviour::Conversion for PaymentAttempt { Self: Sized, { async { - let net_amount = storage_model.get_or_calculate_net_amount(); + let connector_transaction_id = storage_model + .get_optional_connector_transaction_id() + .cloned(); Ok::>(Self { 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, - net_amount, + net_amount: NetAmount::new( + storage_model.amount, + storage_model.shipping_cost, + storage_model.order_tax_amount, + storage_model.surcharge_amount, + storage_model.tax_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, payment_method_id: storage_model.payment_method_id, payment_method: storage_model.payment_method, - connector_transaction_id: storage_model.connector_transaction_id, + connector_transaction_id, capture_method: storage_model.capture_method, capture_on: storage_model.capture_on, confirm: storage_model.confirm, @@ -890,8 +1631,7 @@ impl behaviour::Conversion for PaymentAttempt { customer_acceptance: storage_model.customer_acceptance, profile_id: storage_model.profile_id, organization_id: storage_model.organization_id, - order_tax_amount: storage_model.order_tax_amount, - shipping_cost: storage_model.shipping_cost, + connector_mandate_detail: storage_model.connector_mandate_detail, }) } .await @@ -915,14 +1655,14 @@ impl behaviour::Conversion for PaymentAttempt { merchant_id: self.merchant_id, attempt_id: self.attempt_id, status: self.status, - amount: self.amount, + amount: self.net_amount.get_order_amount(), 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, + surcharge_amount: self.net_amount.get_surcharge_amount(), + tax_amount: self.net_amount.get_tax_on_surcharge(), payment_method_id: self.payment_method_id, payment_method: self.payment_method, capture_method: self.capture_method, @@ -956,7 +1696,7 @@ impl behaviour::Conversion for PaymentAttempt { encoded_data: self.encoded_data, unified_code: self.unified_code, unified_message: self.unified_message, - net_amount: Some(self.net_amount), + net_amount: Some(self.net_amount.get_total_amount()), external_three_ds_authentication_attempted: self .external_three_ds_authentication_attempted, authentication_connector: self.authentication_connector, @@ -971,8 +1711,9 @@ impl behaviour::Conversion for PaymentAttempt { profile_id: self.profile_id, organization_id: self.organization_id, card_network, - order_tax_amount: self.order_tax_amount, - shipping_cost: self.shipping_cost, + order_tax_amount: self.net_amount.get_order_tax_amount(), + shipping_cost: self.net_amount.get_shipping_cost(), + connector_mandate_detail: self.connector_mandate_detail, }) } } @@ -984,10 +1725,12 @@ impl behaviour::Conversion for PaymentAttempt { type NewDstType = DieselPaymentAttemptNew; async fn convert(self) -> CustomResult { + use common_utils::encryption::Encryption; + let card_network = self .payment_method_data .as_ref() - .and_then(|data| data.as_object()) + .and_then(|data| data.peek().as_object()) .and_then(|card| card.get("card")) .and_then(|data| data.as_object()) .and_then(|card| card.get("card_network")) @@ -998,38 +1741,29 @@ impl behaviour::Conversion for PaymentAttempt { payment_id, merchant_id, status, - net_amount, - error_message, - surcharge_amount, - tax_on_surcharge, - confirm, + error, + amount_details, authentication_type, created_at, modified_at, last_synced, cancellation_reason, browser_info, - error_code, payment_token, connector_metadata, payment_experience, payment_method_data, routing_result, preprocessing_step_id, - error_reason, multiple_capture_count, connector_response_reference_id, - amount_capturable, updated_by, - authentication_data, + redirection_data, encoded_data, merchant_connector_id, - unified_code, - unified_message, external_three_ds_authentication_attempted, authentication_connector, authentication_id, - payment_method_billing_address_id, fingerprint_id, charge_id, client_source, @@ -1043,25 +1777,36 @@ impl behaviour::Conversion for PaymentAttempt { authentication_applied, external_reference_id, id, - amount_to_capture, payment_method_id, - shipping_cost, - order_tax_amount, + payment_method_billing_address, connector, + connector_mandate_detail, } = self; + let AttemptAmountDetails { + net_amount, + tax_on_surcharge, + surcharge_amount, + order_tax_amount, + shipping_cost, + amount_capturable, + amount_to_capture, + } = amount_details; + + let (connector_payment_id, connector_payment_data) = connector_payment_id + .map(ConnectorTransactionId::form_id_and_data) + .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) + .unwrap_or((None, None)); + Ok(DieselPaymentAttempt { payment_id, merchant_id, id, status, - error_message, - surcharge_amount, - tax_on_surcharge, + error_message: error.as_ref().map(|details| details.message.clone()), payment_method_id, payment_method_type_v2: payment_method_type, connector_payment_id, - confirm, authentication_type, created_at, modified_at, @@ -1069,29 +1814,32 @@ impl behaviour::Conversion for PaymentAttempt { cancellation_reason, amount_to_capture, browser_info, - error_code, + error_code: error.as_ref().map(|details| details.code.clone()), payment_token, connector_metadata, payment_experience, payment_method_subtype, payment_method_data, preprocessing_step_id, - error_reason, + error_reason: error.as_ref().and_then(|details| details.reason.clone()), multiple_capture_count, connector_response_reference_id, amount_capturable, updated_by, merchant_connector_id, - authentication_data, + redirection_data: redirection_data.map(From::from), encoded_data, - unified_code, - unified_message, - net_amount: Some(net_amount), + unified_code: error + .as_ref() + .and_then(|details| details.unified_code.clone()), + unified_message: error + .as_ref() + .and_then(|details| details.unified_message.clone()), + net_amount, external_three_ds_authentication_attempted, authentication_connector, authentication_id, fingerprint_id, - payment_method_billing_address_id, charge_id, client_source, client_version, @@ -1105,61 +1853,109 @@ impl behaviour::Conversion for PaymentAttempt { authentication_applied, external_reference_id, connector, + surcharge_amount, + tax_on_surcharge, + payment_method_billing_address: payment_method_billing_address.map(Encryption::from), + connector_payment_data, + connector_mandate_detail, }) } async fn convert_back( - _state: &KeyManagerState, + state: &KeyManagerState, storage_model: Self::DstType, - _key: &Secret>, - _key_manager_identifier: keymanager::Identifier, + key: &Secret>, + key_manager_identifier: keymanager::Identifier, ) -> CustomResult where Self: Sized, { async { + let connector_payment_id = storage_model + .get_optional_connector_transaction_id() + .cloned(); + + let decrypted_data = crypto_operation( + state, + common_utils::type_name!(Self::DstType), + CryptoOperation::BatchDecrypt(EncryptedPaymentAttempt::to_encryptable( + EncryptedPaymentAttempt { + payment_method_billing_address: storage_model + .payment_method_billing_address, + }, + )), + key_manager_identifier, + key.peek(), + ) + .await + .and_then(|val| val.try_into_batchoperation())?; + + let decrypted_data = EncryptedPaymentAttempt::from_encryptable(decrypted_data) + .change_context(common_utils::errors::CryptoError::DecodingFailed) + .attach_printable("Invalid batch operation data")?; + + let payment_method_billing_address = decrypted_data + .payment_method_billing_address + .map(|billing| { + billing.deserialize_inner_value(|value| value.parse_value("Address")) + }) + .transpose() + .change_context(common_utils::errors::CryptoError::DecodingFailed) + .attach_printable("Error while deserializing Address")?; + + let amount_details = AttemptAmountDetails { + net_amount: storage_model.net_amount, + tax_on_surcharge: storage_model.tax_on_surcharge, + surcharge_amount: storage_model.surcharge_amount, + order_tax_amount: storage_model.order_tax_amount, + shipping_cost: storage_model.shipping_cost, + amount_capturable: storage_model.amount_capturable, + amount_to_capture: storage_model.amount_to_capture, + }; + + let error = storage_model + .error_code + .zip(storage_model.error_message) + .map(|(error_code, error_message)| ErrorDetails { + code: error_code, + message: error_message, + reason: storage_model.error_reason, + unified_code: storage_model.unified_code, + unified_message: storage_model.unified_message, + }); + Ok::>(Self { payment_id: storage_model.payment_id, merchant_id: storage_model.merchant_id, id: storage_model.id, status: storage_model.status, - net_amount: storage_model.net_amount.unwrap_or(MinorUnit::new(0)), - tax_on_surcharge: storage_model.tax_on_surcharge, - error_message: storage_model.error_message, - surcharge_amount: storage_model.surcharge_amount, + amount_details, + error, payment_method_id: storage_model.payment_method_id, payment_method_type: storage_model.payment_method_type_v2, - connector_payment_id: storage_model.connector_payment_id, - confirm: storage_model.confirm, + connector_payment_id, authentication_type: storage_model.authentication_type, created_at: storage_model.created_at, 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, browser_info: storage_model.browser_info, - error_code: storage_model.error_code, payment_token: storage_model.payment_token, connector_metadata: storage_model.connector_metadata, payment_experience: storage_model.payment_experience, payment_method_data: storage_model.payment_method_data, routing_result: storage_model.routing_result, preprocessing_step_id: storage_model.preprocessing_step_id, - 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, updated_by: storage_model.updated_by, - authentication_data: storage_model.authentication_data, + redirection_data: storage_model.redirection_data.map(From::from), encoded_data: storage_model.encoded_data, merchant_connector_id: storage_model.merchant_connector_id, - unified_code: storage_model.unified_code, - unified_message: storage_model.unified_message, external_three_ds_authentication_attempted: storage_model .external_three_ds_authentication_attempted, authentication_connector: storage_model.authentication_connector, authentication_id: storage_model.authentication_id, - 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, @@ -1167,12 +1963,12 @@ impl behaviour::Conversion for PaymentAttempt { customer_acceptance: storage_model.customer_acceptance, profile_id: storage_model.profile_id, organization_id: storage_model.organization_id, - order_tax_amount: storage_model.order_tax_amount, - shipping_cost: storage_model.shipping_cost, payment_method_subtype: storage_model.payment_method_subtype, authentication_applied: storage_model.authentication_applied, external_reference_id: storage_model.external_reference_id, connector: storage_model.connector, + payment_method_billing_address, + connector_mandate_detail: storage_model.connector_mandate_detail, }) } .await @@ -1182,25 +1978,30 @@ impl behaviour::Conversion for PaymentAttempt { } async fn construct_new(self) -> CustomResult { + use common_utils::encryption::Encryption; + let card_network = self .payment_method_data .as_ref() - .and_then(|data| data.as_object()) + .and_then(|data| data.peek().as_object()) .and_then(|card| card.get("card")) .and_then(|data| data.as_object()) .and_then(|card| card.get("card_network")) .and_then(|network| network.as_str()) .map(|network| network.to_string()); + let error_details = self.error; + Ok(DieselPaymentAttemptNew { payment_id: self.payment_id, merchant_id: self.merchant_id, status: self.status, - error_message: self.error_message, - surcharge_amount: self.surcharge_amount, - tax_on_surcharge: self.tax_on_surcharge, + error_message: error_details + .as_ref() + .map(|details| details.message.clone()), + surcharge_amount: self.amount_details.surcharge_amount, + tax_on_surcharge: self.amount_details.tax_on_surcharge, payment_method_id: self.payment_method_id, - confirm: self.confirm, authentication_type: self.authentication_type, created_at: self.created_at, modified_at: self.modified_at, @@ -1208,28 +2009,33 @@ impl behaviour::Conversion for PaymentAttempt { cancellation_reason: self.cancellation_reason, browser_info: self.browser_info, payment_token: self.payment_token, - error_code: self.error_code, + error_code: error_details.as_ref().map(|details| details.code.clone()), connector_metadata: self.connector_metadata, payment_experience: self.payment_experience, payment_method_data: self.payment_method_data, preprocessing_step_id: self.preprocessing_step_id, - error_reason: self.error_reason, + error_reason: error_details + .as_ref() + .and_then(|details| details.reason.clone()), 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_details.amount_capturable, updated_by: self.updated_by, merchant_connector_id: self.merchant_connector_id, - authentication_data: self.authentication_data, + redirection_data: self.redirection_data.map(From::from), encoded_data: self.encoded_data, - unified_code: self.unified_code, - unified_message: self.unified_message, - net_amount: Some(self.net_amount), + unified_code: error_details + .as_ref() + .and_then(|details| details.unified_code.clone()), + unified_message: error_details + .as_ref() + .and_then(|details| details.unified_message.clone()), + net_amount: self.amount_details.net_amount, external_three_ds_authentication_attempted: self .external_three_ds_authentication_attempted, authentication_connector: self.authentication_connector, authentication_id: self.authentication_id, fingerprint_id: self.fingerprint_id, - payment_method_billing_address_id: self.payment_method_billing_address_id, charge_id: self.charge_id, client_source: self.client_source, client_version: self.client_version, @@ -1237,9 +2043,16 @@ impl behaviour::Conversion for PaymentAttempt { profile_id: self.profile_id, organization_id: self.organization_id, card_network, - order_tax_amount: self.order_tax_amount, - shipping_cost: self.shipping_cost, - amount_to_capture: self.amount_to_capture, + order_tax_amount: self.amount_details.order_tax_amount, + shipping_cost: self.amount_details.shipping_cost, + amount_to_capture: self.amount_details.amount_to_capture, + payment_method_billing_address: self + .payment_method_billing_address + .map(Encryption::from), + payment_method_subtype: self.payment_method_subtype, + payment_method_type_v2: self.payment_method_type, + id: self.id, + connector_mandate_detail: self.connector_mandate_detail, }) } } @@ -1247,6 +2060,145 @@ impl behaviour::Conversion for PaymentAttempt { #[cfg(feature = "v2")] impl From for diesel_models::PaymentAttemptUpdateInternal { fn from(update: PaymentAttemptUpdate) -> Self { - todo!() + match update { + PaymentAttemptUpdate::ConfirmIntent { + status, + updated_by, + connector, + merchant_connector_id, + } => Self { + status: Some(status), + error_message: None, + modified_at: common_utils::date_time::now(), + browser_info: None, + error_code: None, + error_reason: None, + updated_by, + merchant_connector_id: Some(merchant_connector_id), + unified_code: None, + unified_message: None, + connector_payment_id: None, + connector: Some(connector), + redirection_data: None, + connector_metadata: None, + amount_capturable: None, + amount_to_capture: None, + }, + PaymentAttemptUpdate::ErrorUpdate { + status, + error, + connector_payment_id, + amount_capturable, + updated_by, + } => Self { + status: Some(status), + error_message: Some(error.message), + error_code: Some(error.code), + modified_at: common_utils::date_time::now(), + browser_info: None, + error_reason: error.reason, + updated_by, + merchant_connector_id: None, + unified_code: None, + unified_message: None, + connector_payment_id, + connector: None, + redirection_data: None, + connector_metadata: None, + amount_capturable, + amount_to_capture: None, + }, + PaymentAttemptUpdate::ConfirmIntentResponse { + status, + connector_payment_id, + updated_by, + redirection_data, + connector_metadata, + amount_capturable, + } => Self { + status: Some(status), + amount_capturable, + error_message: None, + error_code: None, + modified_at: common_utils::date_time::now(), + browser_info: None, + error_reason: None, + updated_by, + merchant_connector_id: None, + unified_code: None, + unified_message: None, + connector_payment_id, + connector: None, + redirection_data: redirection_data + .map(diesel_models::payment_attempt::RedirectForm::from), + connector_metadata, + amount_to_capture: None, + }, + PaymentAttemptUpdate::SyncUpdate { + status, + amount_capturable, + updated_by, + } => Self { + status: Some(status), + amount_capturable, + error_message: None, + error_code: None, + modified_at: common_utils::date_time::now(), + browser_info: None, + error_reason: None, + updated_by, + merchant_connector_id: None, + unified_code: None, + unified_message: None, + connector_payment_id: None, + connector: None, + redirection_data: None, + connector_metadata: None, + amount_to_capture: None, + }, + PaymentAttemptUpdate::CaptureUpdate { + status, + amount_capturable, + updated_by, + } => Self { + status: Some(status), + amount_capturable, + amount_to_capture: None, + error_message: None, + error_code: None, + modified_at: common_utils::date_time::now(), + browser_info: None, + error_reason: None, + updated_by, + merchant_connector_id: None, + unified_code: None, + unified_message: None, + connector_payment_id: None, + connector: None, + redirection_data: None, + connector_metadata: None, + }, + PaymentAttemptUpdate::PreCaptureUpdate { + amount_to_capture, + updated_by, + } => Self { + amount_to_capture, + error_message: None, + modified_at: common_utils::date_time::now(), + browser_info: None, + error_code: None, + error_reason: None, + updated_by, + merchant_connector_id: None, + unified_code: None, + unified_message: None, + connector_payment_id: None, + connector: None, + redirection_data: None, + status: None, + connector_metadata: None, + amount_capturable: None, + }, + } } } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs index bd4707243ceb..0afefbab9a46 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs @@ -1,4 +1,5 @@ -use common_enums as storage_enums; +#[cfg(feature = "v2")] +use common_utils::ext_traits::{Encode, ValueExt}; use common_utils::{ consts::{PAYMENTS_LIST_MAX_LIMIT_V1, PAYMENTS_LIST_MAX_LIMIT_V2}, crypto::Encryptable, @@ -8,7 +9,7 @@ use common_utils::{ pii::{self, Email}, type_name, types::{ - keymanager::{self, KeyManagerState}, + keymanager::{self, KeyManagerState, ToEncryptable}, MinorUnit, }, }; @@ -16,6 +17,8 @@ use diesel_models::{ PaymentIntent as DieselPaymentIntent, PaymentIntentNew as DieselPaymentIntentNew, }; use error_stack::ResultExt; +#[cfg(feature = "v2")] +use masking::ExposeInterface; use masking::{Deserialize, PeekInterface, Secret}; use serde::Serialize; use time::PrimitiveDateTime; @@ -23,12 +26,15 @@ use time::PrimitiveDateTime; #[cfg(all(feature = "v1", feature = "olap"))] use super::payment_attempt::PaymentAttempt; use super::PaymentIntent; +#[cfg(feature = "v2")] +use crate::address::Address; use crate::{ behaviour, errors, merchant_key_store::MerchantKeyStore, - type_encryption::{crypto_operation, AsyncLift, CryptoOperation}, + type_encryption::{crypto_operation, CryptoOperation}, RemoteStorageObject, }; + #[async_trait::async_trait] pub trait PaymentIntentInterface { async fn update_payment_intent( @@ -37,7 +43,7 @@ pub trait PaymentIntentInterface { this: PaymentIntent, payment_intent: PaymentIntentUpdate, merchant_key_store: &MerchantKeyStore, - storage_scheme: storage_enums::MerchantStorageScheme, + storage_scheme: common_enums::MerchantStorageScheme, ) -> error_stack::Result; async fn insert_payment_intent( @@ -45,7 +51,7 @@ pub trait PaymentIntentInterface { state: &KeyManagerState, new: PaymentIntent, merchant_key_store: &MerchantKeyStore, - storage_scheme: storage_enums::MerchantStorageScheme, + storage_scheme: common_enums::MerchantStorageScheme, ) -> error_stack::Result; #[cfg(feature = "v1")] @@ -55,7 +61,7 @@ pub trait PaymentIntentInterface { payment_id: &id_type::PaymentId, merchant_id: &id_type::MerchantId, merchant_key_store: &MerchantKeyStore, - storage_scheme: storage_enums::MerchantStorageScheme, + storage_scheme: common_enums::MerchantStorageScheme, ) -> error_stack::Result; #[cfg(feature = "v2")] @@ -64,7 +70,7 @@ pub trait PaymentIntentInterface { state: &KeyManagerState, id: &id_type::GlobalPaymentId, merchant_key_store: &MerchantKeyStore, - storage_scheme: storage_enums::MerchantStorageScheme, + storage_scheme: common_enums::MerchantStorageScheme, ) -> error_stack::Result; #[cfg(all(feature = "v1", feature = "olap"))] @@ -74,7 +80,7 @@ pub trait PaymentIntentInterface { merchant_id: &id_type::MerchantId, filters: &PaymentIntentFetchConstraints, merchant_key_store: &MerchantKeyStore, - storage_scheme: storage_enums::MerchantStorageScheme, + storage_scheme: common_enums::MerchantStorageScheme, ) -> error_stack::Result, errors::StorageError>; #[cfg(all(feature = "v1", feature = "olap"))] @@ -84,7 +90,7 @@ pub trait PaymentIntentInterface { merchant_id: &id_type::MerchantId, time_range: &common_utils::types::TimeRange, merchant_key_store: &MerchantKeyStore, - storage_scheme: storage_enums::MerchantStorageScheme, + storage_scheme: common_enums::MerchantStorageScheme, ) -> error_stack::Result, errors::StorageError>; #[cfg(all(feature = "v1", feature = "olap"))] @@ -102,7 +108,7 @@ pub trait PaymentIntentInterface { merchant_id: &id_type::MerchantId, constraints: &PaymentIntentFetchConstraints, merchant_key_store: &MerchantKeyStore, - storage_scheme: storage_enums::MerchantStorageScheme, + storage_scheme: common_enums::MerchantStorageScheme, ) -> error_stack::Result, errors::StorageError>; #[cfg(all(feature = "v1", feature = "olap"))] @@ -110,7 +116,7 @@ pub trait PaymentIntentInterface { &self, merchant_id: &id_type::MerchantId, constraints: &PaymentIntentFetchConstraints, - storage_scheme: storage_enums::MerchantStorageScheme, + storage_scheme: common_enums::MerchantStorageScheme, ) -> error_stack::Result, errors::StorageError>; } @@ -126,39 +132,52 @@ pub struct CustomerData { #[derive(Debug, Clone, Serialize)] pub struct PaymentIntentUpdateFields { pub amount: Option, - pub currency: Option, - pub setup_future_usage: Option, - pub status: storage_enums::IntentStatus, - pub customer_id: Option, - pub shipping_address: Option>>, - pub billing_address: Option>>, - pub return_url: Option, - pub description: Option, - pub statement_descriptor: Option, - pub order_details: Option>, + pub currency: Option, + pub shipping_cost: Option, + pub tax_details: Option, + pub skip_external_tax_calculation: Option, + pub skip_surcharge_calculation: Option, + pub surcharge_amount: Option, + pub tax_on_surcharge: Option, + pub routing_algorithm_id: Option, + pub capture_method: Option, + pub authentication_type: Option, + pub billing_address: Option>, + pub shipping_address: Option>, + pub customer_present: Option, + pub description: Option, + pub return_url: Option, + pub setup_future_usage: Option, + pub apply_mit_exemption: Option, + pub statement_descriptor: Option, + pub order_details: Option>>, + pub allowed_payment_method_types: Option>, pub metadata: Option, - pub payment_confirm_source: Option, - pub updated_by: String, + pub connector_metadata: Option, + pub feature_metadata: Option, + pub payment_link_config: Option, + pub request_incremental_authorization: Option, pub session_expiry: Option, - pub request_external_three_ds_authentication: Option, pub frm_metadata: Option, - pub customer_details: Option>>, - pub merchant_order_reference_id: Option, - pub is_payment_processor_token_flow: Option, + pub request_external_three_ds_authentication: + Option, + + // updated_by is set internally, field not present in request + pub updated_by: String, } #[cfg(feature = "v1")] #[derive(Debug, Clone, Serialize)] pub struct PaymentIntentUpdateFields { pub amount: MinorUnit, - pub currency: storage_enums::Currency, - pub setup_future_usage: Option, - pub status: storage_enums::IntentStatus, + pub currency: common_enums::Currency, + pub setup_future_usage: Option, + pub status: common_enums::IntentStatus, pub customer_id: Option, pub shipping_address_id: Option, pub billing_address_id: Option, pub return_url: Option, - pub business_country: Option, + pub business_country: Option, pub business_label: Option, pub description: Option, pub statement_descriptor_name: Option, @@ -166,7 +185,7 @@ pub struct PaymentIntentUpdateFields { pub order_details: Option>, pub metadata: Option, pub frm_metadata: Option, - pub payment_confirm_source: Option, + pub payment_confirm_source: Option, pub updated_by: String, pub fingerprint_id: Option, pub session_expiry: Option, @@ -183,9 +202,8 @@ pub struct PaymentIntentUpdateFields { #[derive(Debug, Clone, Serialize)] pub enum PaymentIntentUpdate { ResponseUpdate { - status: storage_enums::IntentStatus, + status: common_enums::IntentStatus, amount_captured: Option, - return_url: Option, updated_by: String, fingerprint_id: Option, incremental_authorization_allowed: Option, @@ -197,7 +215,7 @@ pub enum PaymentIntentUpdate { Update(Box), PaymentCreateUpdate { return_url: Option, - status: Option, + status: Option, customer_id: Option, shipping_address_id: Option, billing_address_id: Option, @@ -205,13 +223,13 @@ pub enum PaymentIntentUpdate { updated_by: String, }, MerchantStatusUpdate { - status: storage_enums::IntentStatus, + status: common_enums::IntentStatus, shipping_address_id: Option, billing_address_id: Option, updated_by: String, }, PGStatusUpdate { - status: storage_enums::IntentStatus, + status: common_enums::IntentStatus, incremental_authorization_allowed: Option, updated_by: String, }, @@ -221,18 +239,18 @@ pub enum PaymentIntentUpdate { updated_by: String, }, StatusAndAttemptUpdate { - status: storage_enums::IntentStatus, + status: common_enums::IntentStatus, active_attempt_id: String, attempt_count: i16, updated_by: String, }, ApproveUpdate { - status: storage_enums::IntentStatus, + status: common_enums::IntentStatus, merchant_decision: Option, updated_by: String, }, RejectUpdate { - status: storage_enums::IntentStatus, + status: common_enums::IntentStatus, merchant_decision: Option, updated_by: String, }, @@ -250,7 +268,7 @@ pub enum PaymentIntentUpdate { shipping_address_id: Option, }, ManualUpdate { - status: Option, + status: Option, updated_by: String, }, SessionResponseUpdate { @@ -261,130 +279,53 @@ pub enum PaymentIntentUpdate { }, } -// TODO: remove all enum variants and create new variants that should be used for v2 #[cfg(feature = "v2")] #[derive(Debug, Clone, Serialize)] pub enum PaymentIntentUpdate { - ResponseUpdate { - status: storage_enums::IntentStatus, - amount_captured: Option, - return_url: Option, - updated_by: String, - }, - MetadataUpdate { - metadata: pii::SecretSerdeValue, - updated_by: String, - }, - Update(Box), - PaymentCreateUpdate { - return_url: Option, - status: Option, - customer_id: Option, - shipping_address: Option>>, - billing_address: Option>>, - customer_details: Option>>, - updated_by: String, - }, - MerchantStatusUpdate { - status: storage_enums::IntentStatus, - shipping_address: Option>>, - billing_address: Option>>, - updated_by: String, - }, - PGStatusUpdate { - status: storage_enums::IntentStatus, - updated_by: String, - }, - PaymentAttemptAndAttemptCountUpdate { - active_attempt_id: String, - attempt_count: i16, - updated_by: String, - }, - StatusAndAttemptUpdate { - status: storage_enums::IntentStatus, - active_attempt_id: String, - attempt_count: i16, - updated_by: String, - }, - ApproveUpdate { - status: storage_enums::IntentStatus, - frm_merchant_decision: Option, + /// PreUpdate tracker of ConfirmIntent + ConfirmIntent { + status: common_enums::IntentStatus, + active_attempt_id: id_type::GlobalAttemptId, updated_by: String, }, - RejectUpdate { - status: storage_enums::IntentStatus, - frm_merchant_decision: Option, + /// PostUpdate tracker of ConfirmIntent + ConfirmIntentPostUpdate { + status: common_enums::IntentStatus, + amount_captured: Option, updated_by: String, }, - SurchargeApplicableUpdate { - surcharge_applicable: bool, + /// SyncUpdate of ConfirmIntent in PostUpdateTrackers + SyncUpdate { + status: common_enums::IntentStatus, + amount_captured: Option, updated_by: String, }, - IncrementalAuthorizationAmountUpdate { - amount: MinorUnit, - }, - AuthorizationCountUpdate { - authorization_count: i32, - }, - CompleteAuthorizeUpdate { - shipping_address: Option>>, - }, - ManualUpdate { - status: Option, + CaptureUpdate { + status: common_enums::IntentStatus, + amount_captured: Option, updated_by: String, }, -} - -#[cfg(feature = "v2")] -#[derive(Clone, Debug, Default)] -pub struct PaymentIntentUpdateInternal { - pub amount: Option, - pub currency: Option, - pub status: Option, - pub amount_captured: Option, - pub customer_id: Option, - pub return_url: Option, - pub setup_future_usage: Option, - pub off_session: Option, - pub metadata: Option, - pub modified_at: Option, - pub active_attempt_id: Option, - pub description: Option, - pub statement_descriptor: Option, - pub order_details: Option>, - pub attempt_count: Option, - pub frm_merchant_decision: Option, - pub payment_confirm_source: Option, - pub updated_by: String, - pub surcharge_applicable: Option, - pub authorization_count: Option, - pub session_expiry: Option, - pub request_external_three_ds_authentication: Option, - pub frm_metadata: Option, - pub customer_details: Option>>, - pub billing_address: Option>>, - pub shipping_address: Option>>, - pub merchant_order_reference_id: Option, - pub is_payment_processor_token_flow: Option, + /// UpdateIntent + UpdateIntent(Box), } #[cfg(feature = "v1")] #[derive(Clone, Debug, Default)] pub struct PaymentIntentUpdateInternal { pub amount: Option, - pub currency: Option, - pub status: Option, + pub currency: Option, + pub status: Option, pub amount_captured: Option, pub customer_id: Option, pub return_url: Option, - pub setup_future_usage: Option, + pub setup_future_usage: Option, pub off_session: Option, pub metadata: Option, pub billing_address_id: Option, pub shipping_address_id: Option, pub modified_at: Option, pub active_attempt_id: Option, - pub business_country: Option, + pub business_country: Option, pub business_label: Option, pub description: Option, pub statement_descriptor_name: Option, @@ -394,7 +335,7 @@ pub struct PaymentIntentUpdateInternal { // Denotes the action(approve or reject) taken by merchant in case of manual review. // Manual review can occur when the transaction is marked as risky by the frm_processor, payment processor or when there is underpayment/over payment incase of crypto payment pub merchant_decision: Option, - pub payment_confirm_source: Option, + pub payment_confirm_source: Option, pub updated_by: String, pub surcharge_applicable: Option, @@ -412,181 +353,250 @@ pub struct PaymentIntentUpdateInternal { pub tax_details: Option, } +// This conversion is used in the `update_payment_intent` function #[cfg(feature = "v2")] -impl From for PaymentIntentUpdateInternal { +impl From for diesel_models::PaymentIntentUpdateInternal { fn from(payment_intent_update: PaymentIntentUpdate) -> Self { - todo!() - // match payment_intent_update { - // PaymentIntentUpdate::MetadataUpdate { - // metadata, - // updated_by, - // } => Self { - // metadata: Some(metadata), - // modified_at: Some(common_utils::date_time::now()), - // updated_by, - // ..Default::default() - // }, - // PaymentIntentUpdate::Update(value) => Self { - // amount: Some(value.amount), - // currency: Some(value.currency), - // setup_future_usage: value.setup_future_usage, - // status: Some(value.status), - // customer_id: value.customer_id, - // return_url: value.return_url, - // description: value.description, - // statement_descriptor: value.statement_descriptor, - // order_details: value.order_details, - // metadata: value.metadata, - // payment_confirm_source: value.payment_confirm_source, - // updated_by: value.updated_by, - // session_expiry: value.session_expiry, - // request_external_three_ds_authentication: value - // .request_external_three_ds_authentication, - // frm_metadata: value.frm_metadata, - // customer_details: value.customer_details, - // billing_address: value.billing_address, - // merchant_order_reference_id: value.merchant_order_reference_id, - // shipping_address: value.shipping_address, - // is_payment_processor_token_flow: value.is_payment_processor_token_flow, - // modified_at: Some(common_utils::date_time::now()), - // ..Default::default() - // }, - // PaymentIntentUpdate::PaymentCreateUpdate { - // return_url, - // status, - // customer_id, - // shipping_address, - // billing_address, - // customer_details, - // updated_by, - // } => Self { - // return_url, - // status, - // customer_id, - // shipping_address, - // billing_address, - // customer_details, - // modified_at: Some(common_utils::date_time::now()), - // updated_by, - // ..Default::default() - // }, - // PaymentIntentUpdate::PGStatusUpdate { status, updated_by } => Self { - // status: Some(status), - // modified_at: Some(common_utils::date_time::now()), - // updated_by, - // ..Default::default() - // }, - // PaymentIntentUpdate::MerchantStatusUpdate { - // status, - // shipping_address, - // billing_address, - // updated_by, - // } => Self { - // status: Some(status), - // shipping_address, - // billing_address, - // modified_at: Some(common_utils::date_time::now()), - // updated_by, - // ..Default::default() - // }, - // PaymentIntentUpdate::ResponseUpdate { - // // amount, - // // currency, - // status, - // amount_captured, - // // customer_id, - // return_url, - // updated_by, - // } => Self { - // // amount, - // // currency: Some(currency), - // status: Some(status), - // amount_captured, - // // customer_id, - // return_url, - // modified_at: Some(common_utils::date_time::now()), - // updated_by, - // ..Default::default() - // }, - // PaymentIntentUpdate::PaymentAttemptAndAttemptCountUpdate { - // active_attempt_id, - // attempt_count, - // updated_by, - // } => Self { - // active_attempt_id: Some(active_attempt_id), - // attempt_count: Some(attempt_count), - // updated_by, - // modified_at: Some(common_utils::date_time::now()), - // ..Default::default() - // }, - // PaymentIntentUpdate::StatusAndAttemptUpdate { - // status, - // active_attempt_id, - // attempt_count, - // updated_by, - // } => Self { - // status: Some(status), - // active_attempt_id: Some(active_attempt_id), - // attempt_count: Some(attempt_count), - // updated_by, - // modified_at: Some(common_utils::date_time::now()), - // ..Default::default() - // }, - // PaymentIntentUpdate::ApproveUpdate { - // status, - // frm_merchant_decision, - // updated_by, - // } => Self { - // status: Some(status), - // frm_merchant_decision, - // updated_by, - // modified_at: Some(common_utils::date_time::now()), - // ..Default::default() - // }, - // PaymentIntentUpdate::RejectUpdate { - // status, - // frm_merchant_decision, - // updated_by, - // } => Self { - // status: Some(status), - // frm_merchant_decision, - // updated_by, - // modified_at: Some(common_utils::date_time::now()), - // ..Default::default() - // }, - // PaymentIntentUpdate::SurchargeApplicableUpdate { - // surcharge_applicable, - // updated_by, - // } => Self { - // surcharge_applicable: Some(surcharge_applicable), - // modified_at: Some(common_utils::date_time::now()), - // updated_by, - // ..Default::default() - // }, - // PaymentIntentUpdate::IncrementalAuthorizationAmountUpdate { amount } => Self { - // amount: Some(amount), - // modified_at: Some(common_utils::date_time::now()), - // ..Default::default() - // }, - // PaymentIntentUpdate::AuthorizationCountUpdate { - // authorization_count, - // } => Self { - // authorization_count: Some(authorization_count), - // modified_at: Some(common_utils::date_time::now()), - // ..Default::default() - // }, - // PaymentIntentUpdate::CompleteAuthorizeUpdate { shipping_address } => Self { - // shipping_address, - // modified_at: Some(common_utils::date_time::now()), - // ..Default::default() - // }, - // PaymentIntentUpdate::ManualUpdate { status, updated_by } => Self { - // status, - // modified_at: Some(common_utils::date_time::now()), - // updated_by, - // ..Default::default() - // }, - // } + match payment_intent_update { + PaymentIntentUpdate::ConfirmIntent { + status, + active_attempt_id, + updated_by, + } => Self { + status: Some(status), + active_attempt_id: Some(active_attempt_id), + modified_at: common_utils::date_time::now(), + amount: None, + amount_captured: None, + currency: None, + shipping_cost: None, + tax_details: None, + skip_external_tax_calculation: None, + surcharge_applicable: None, + surcharge_amount: None, + tax_on_surcharge: None, + routing_algorithm_id: None, + capture_method: None, + authentication_type: None, + billing_address: None, + shipping_address: None, + customer_present: None, + description: None, + return_url: None, + setup_future_usage: None, + apply_mit_exemption: None, + statement_descriptor: None, + order_details: None, + allowed_payment_method_types: None, + metadata: None, + connector_metadata: None, + feature_metadata: None, + payment_link_config: None, + request_incremental_authorization: None, + session_expiry: None, + frm_metadata: None, + request_external_three_ds_authentication: None, + updated_by, + }, + + PaymentIntentUpdate::ConfirmIntentPostUpdate { + status, + updated_by, + amount_captured, + } => Self { + status: Some(status), + active_attempt_id: None, + modified_at: common_utils::date_time::now(), + amount_captured, + amount: None, + currency: None, + shipping_cost: None, + tax_details: None, + skip_external_tax_calculation: None, + surcharge_applicable: None, + surcharge_amount: None, + tax_on_surcharge: None, + routing_algorithm_id: None, + capture_method: None, + authentication_type: None, + billing_address: None, + shipping_address: None, + customer_present: None, + description: None, + return_url: None, + setup_future_usage: None, + apply_mit_exemption: None, + statement_descriptor: None, + order_details: None, + allowed_payment_method_types: None, + metadata: None, + connector_metadata: None, + feature_metadata: None, + payment_link_config: None, + request_incremental_authorization: None, + session_expiry: None, + frm_metadata: None, + request_external_three_ds_authentication: None, + updated_by, + }, + PaymentIntentUpdate::SyncUpdate { + status, + amount_captured, + updated_by, + } => Self { + status: Some(status), + active_attempt_id: None, + modified_at: common_utils::date_time::now(), + amount: None, + currency: None, + amount_captured, + shipping_cost: None, + tax_details: None, + skip_external_tax_calculation: None, + surcharge_applicable: None, + surcharge_amount: None, + tax_on_surcharge: None, + routing_algorithm_id: None, + capture_method: None, + authentication_type: None, + billing_address: None, + shipping_address: None, + customer_present: None, + description: None, + return_url: None, + setup_future_usage: None, + apply_mit_exemption: None, + statement_descriptor: None, + order_details: None, + allowed_payment_method_types: None, + metadata: None, + connector_metadata: None, + feature_metadata: None, + payment_link_config: None, + request_incremental_authorization: None, + session_expiry: None, + frm_metadata: None, + request_external_three_ds_authentication: None, + updated_by, + }, + PaymentIntentUpdate::CaptureUpdate { + status, + amount_captured, + updated_by, + } => Self { + status: Some(status), + amount_captured, + active_attempt_id: None, + modified_at: common_utils::date_time::now(), + amount: None, + currency: None, + shipping_cost: None, + tax_details: None, + skip_external_tax_calculation: None, + surcharge_applicable: None, + surcharge_amount: None, + tax_on_surcharge: None, + routing_algorithm_id: None, + capture_method: None, + authentication_type: None, + billing_address: None, + shipping_address: None, + customer_present: None, + description: None, + return_url: None, + setup_future_usage: None, + apply_mit_exemption: None, + statement_descriptor: None, + order_details: None, + allowed_payment_method_types: None, + metadata: None, + connector_metadata: None, + feature_metadata: None, + payment_link_config: None, + request_incremental_authorization: None, + session_expiry: None, + frm_metadata: None, + request_external_three_ds_authentication: None, + updated_by, + }, + PaymentIntentUpdate::UpdateIntent(boxed_intent) => { + let PaymentIntentUpdateFields { + amount, + currency, + shipping_cost, + tax_details, + skip_external_tax_calculation, + skip_surcharge_calculation, + surcharge_amount, + tax_on_surcharge, + routing_algorithm_id, + capture_method, + authentication_type, + billing_address, + shipping_address, + customer_present, + description, + return_url, + setup_future_usage, + apply_mit_exemption, + statement_descriptor, + order_details, + allowed_payment_method_types, + metadata, + connector_metadata, + feature_metadata, + payment_link_config, + request_incremental_authorization, + session_expiry, + frm_metadata, + request_external_three_ds_authentication, + updated_by, + } = *boxed_intent; + Self { + status: None, + active_attempt_id: None, + modified_at: common_utils::date_time::now(), + amount_captured: None, + amount, + currency, + shipping_cost, + tax_details, + skip_external_tax_calculation: skip_external_tax_calculation + .map(|val| val.as_bool()), + surcharge_applicable: skip_surcharge_calculation.map(|val| val.as_bool()), + surcharge_amount, + tax_on_surcharge, + routing_algorithm_id, + capture_method, + authentication_type, + billing_address: billing_address.map(Encryption::from), + shipping_address: shipping_address.map(Encryption::from), + customer_present: customer_present.map(|val| val.as_bool()), + description, + return_url, + setup_future_usage, + apply_mit_exemption: apply_mit_exemption.map(|val| val.as_bool()), + statement_descriptor, + order_details, + allowed_payment_method_types: allowed_payment_method_types + .map(|allowed_payment_method_types| { + allowed_payment_method_types.encode_to_value() + }) + .and_then(|r| r.ok().map(Secret::new)), + metadata, + connector_metadata, + feature_metadata, + payment_link_config, + request_incremental_authorization, + session_expiry, + frm_metadata, + request_external_three_ds_authentication: + request_external_three_ds_authentication.map(|val| val.as_bool()), + + updated_by, + } + } + } } } @@ -683,7 +693,6 @@ impl From for PaymentIntentUpdateInternal { amount_captured, fingerprint_id, // customer_id, - return_url, updated_by, incremental_authorization_allowed, } => Self { @@ -693,7 +702,6 @@ impl From for PaymentIntentUpdateInternal { amount_captured, fingerprint_id, // customer_id, - return_url, modified_at: Some(common_utils::date_time::now()), updated_by, incremental_authorization_allowed, @@ -787,153 +795,26 @@ impl From for PaymentIntentUpdateInternal { } } +#[cfg(feature = "v1")] use diesel_models::{ PaymentIntentUpdate as DieselPaymentIntentUpdate, PaymentIntentUpdateFields as DieselPaymentIntentUpdateFields, }; -#[cfg(feature = "v2")] -impl From for DieselPaymentIntentUpdate { - fn from(value: PaymentIntentUpdate) -> Self { - todo!() - // match value { - // PaymentIntentUpdate::ResponseUpdate { - // status, - // amount_captured, - // return_url, - // updated_by, - // } => Self::ResponseUpdate { - // status, - // amount_captured, - // return_url, - // updated_by, - // }, - // PaymentIntentUpdate::MetadataUpdate { - // metadata, - // updated_by, - // } => Self::MetadataUpdate { - // metadata, - // updated_by, - // }, - // PaymentIntentUpdate::Update(value) => { - // Self::Update(Box::new(DieselPaymentIntentUpdateFields { - // amount: value.amount, - // currency: value.currency, - // setup_future_usage: value.setup_future_usage, - // status: value.status, - // customer_id: value.customer_id, - // return_url: value.return_url, - // description: value.description, - // statement_descriptor: value.statement_descriptor, - // order_details: value.order_details, - // metadata: value.metadata, - // payment_confirm_source: value.payment_confirm_source, - // updated_by: value.updated_by, - // session_expiry: value.session_expiry, - // request_external_three_ds_authentication: value - // .request_external_three_ds_authentication, - // frm_metadata: value.frm_metadata, - // customer_details: value.customer_details.map(Encryption::from), - // billing_address: value.billing_address.map(Encryption::from), - // shipping_address: value.shipping_address.map(Encryption::from), - // merchant_order_reference_id: value.merchant_order_reference_id, - // is_payment_processor_token_flow: value.is_payment_processor_token_flow, - // })) - // } - // PaymentIntentUpdate::PaymentCreateUpdate { - // return_url, - // status, - // customer_id, - // shipping_address, - // billing_address, - // customer_details, - // updated_by, - // } => Self::PaymentCreateUpdate { - // return_url, - // status, - // customer_id, - // shipping_address: shipping_address.map(Encryption::from), - // billing_address: billing_address.map(Encryption::from), - // customer_details: customer_details.map(Encryption::from), - // updated_by, - // }, - // PaymentIntentUpdate::MerchantStatusUpdate { - // status, - // shipping_address, - // billing_address, - // updated_by, - // } => Self::MerchantStatusUpdate { - // status, - // shipping_address: shipping_address.map(Encryption::from), - // billing_address: billing_address.map(Encryption::from), - // updated_by, - // }, - // PaymentIntentUpdate::PGStatusUpdate { status, updated_by } => { - // Self::PGStatusUpdate { status, updated_by } - // } - // PaymentIntentUpdate::PaymentAttemptAndAttemptCountUpdate { - // active_attempt_id, - // attempt_count, - // updated_by, - // } => Self::PaymentAttemptAndAttemptCountUpdate { - // active_attempt_id, - // attempt_count, - // updated_by, - // }, - // PaymentIntentUpdate::StatusAndAttemptUpdate { - // status, - // active_attempt_id, - // attempt_count, - // updated_by, - // } => Self::StatusAndAttemptUpdate { - // status, - // active_attempt_id, - // attempt_count, - // updated_by, - // }, - // PaymentIntentUpdate::ApproveUpdate { - // status, - // frm_merchant_decision, - // updated_by, - // } => Self::ApproveUpdate { - // status, - // frm_merchant_decision, - // updated_by, - // }, - // PaymentIntentUpdate::RejectUpdate { - // status, - // frm_merchant_decision, - // updated_by, - // } => Self::RejectUpdate { - // status, - // frm_merchant_decision, - // updated_by, - // }, - // PaymentIntentUpdate::SurchargeApplicableUpdate { - // surcharge_applicable, - // updated_by, - // } => Self::SurchargeApplicableUpdate { - // surcharge_applicable: Some(surcharge_applicable), - // updated_by, - // }, - // PaymentIntentUpdate::IncrementalAuthorizationAmountUpdate { amount } => { - // Self::IncrementalAuthorizationAmountUpdate { amount } - // } - // PaymentIntentUpdate::AuthorizationCountUpdate { - // authorization_count, - // } => Self::AuthorizationCountUpdate { - // authorization_count, - // }, - // PaymentIntentUpdate::CompleteAuthorizeUpdate { shipping_address } => { - // Self::CompleteAuthorizeUpdate { - // shipping_address: shipping_address.map(Encryption::from), - // } - // } - // PaymentIntentUpdate::ManualUpdate { status, updated_by } => { - // Self::ManualUpdate { status, updated_by } - // } - // } - } -} + +// TODO: check where this conversion is used +// #[cfg(feature = "v2")] +// impl From for DieselPaymentIntentUpdate { +// fn from(value: PaymentIntentUpdate) -> Self { +// match value { +// PaymentIntentUpdate::ConfirmIntent { status, updated_by } => { +// Self::ConfirmIntent { status, updated_by } +// } +// PaymentIntentUpdate::ConfirmIntentPostUpdate { status, updated_by } => { +// Self::ConfirmIntentPostUpdate { status, updated_by } +// } +// } +// } +// } #[cfg(feature = "v1")] impl From for DieselPaymentIntentUpdate { @@ -943,14 +824,12 @@ impl From for DieselPaymentIntentUpdate { status, amount_captured, fingerprint_id, - return_url, updated_by, incremental_authorization_allowed, } => Self::ResponseUpdate { status, amount_captured, fingerprint_id, - return_url, updated_by, incremental_authorization_allowed, }, @@ -1106,75 +985,6 @@ impl From for DieselPaymentIntentUpdate { } } -// TODO: evaluate if we will be using the same update struct for v2 as well, uncomment this and make necessary changes if necessary -#[cfg(feature = "v2")] -impl From for diesel_models::PaymentIntentUpdateInternal { - fn from(value: PaymentIntentUpdateInternal) -> Self { - todo!() - // let modified_at = common_utils::date_time::now(); - // let PaymentIntentUpdateInternal { - // amount, - // currency, - // status, - // amount_captured, - // customer_id, - // return_url, - // setup_future_usage, - // off_session, - // metadata, - // modified_at: _, - // active_attempt_id, - // description, - // statement_descriptor, - // order_details, - // attempt_count, - // frm_merchant_decision, - // payment_confirm_source, - // updated_by, - // surcharge_applicable, - // authorization_count, - // session_expiry, - // request_external_three_ds_authentication, - // frm_metadata, - // customer_details, - // billing_address, - // merchant_order_reference_id, - // shipping_address, - // is_payment_processor_token_flow, - // } = value; - // Self { - // amount, - // currency, - // status, - // amount_captured, - // customer_id, - // return_url, - // setup_future_usage, - // off_session, - // metadata, - // modified_at, - // active_attempt_id, - // description, - // statement_descriptor, - // order_details, - // attempt_count, - // frm_merchant_decision, - // payment_confirm_source, - // updated_by, - // surcharge_applicable, - // authorization_count, - // session_expiry, - // request_external_three_ds_authentication, - // frm_metadata, - // customer_details: customer_details.map(Encryption::from), - // billing_address: billing_address.map(Encryption::from), - // merchant_order_reference_id, - // shipping_address: shipping_address.map(Encryption::from), - // is_payment_processor_token_flow, - // } - } -} - #[cfg(feature = "v1")] impl From for diesel_models::PaymentIntentUpdateInternal { fn from(value: PaymentIntentUpdateInternal) -> Self { @@ -1281,11 +1091,11 @@ pub struct PaymentIntentListParams { pub ending_at: Option, pub amount_filter: Option, pub connector: Option>, - pub currency: Option>, - pub status: Option>, - pub payment_method: Option>, - pub payment_method_type: Option>, - pub authentication_type: Option>, + pub currency: Option>, + pub status: Option>, + pub payment_method: Option>, + pub payment_method_type: Option>, + pub authentication_type: Option>, pub merchant_connector_id: Option>, pub profile_id: Option>, pub customer_id: Option, @@ -1293,6 +1103,8 @@ pub struct PaymentIntentListParams { pub ending_before_id: Option, pub limit: Option, pub order: api_models::payments::Order, + pub card_network: Option>, + pub merchant_order_reference_id: Option, } impl From for PaymentIntentFetchConstraints { @@ -1326,6 +1138,8 @@ impl From for PaymentIntentFetchCo ending_before_id: ending_before, limit: Some(std::cmp::min(limit, PAYMENTS_LIST_MAX_LIMIT_V1)), order: Default::default(), + card_network: None, + merchant_order_reference_id: None, })) } } @@ -1350,6 +1164,8 @@ impl From for PaymentIntentFetchConstraints { ending_before_id: None, limit: None, order: Default::default(), + card_network: None, + merchant_order_reference_id: None, })) } } @@ -1372,6 +1188,8 @@ impl From for PaymentIntentF authentication_type, merchant_connector_id, order, + card_network, + merchant_order_reference_id, } = value; if let Some(payment_intent_id) = payment_id { Self::Single { payment_intent_id } @@ -1394,6 +1212,8 @@ impl From for PaymentIntentF ending_before_id: None, limit: Some(std::cmp::min(limit, PAYMENTS_LIST_MAX_LIMIT_V2)), order, + card_network, + merchant_order_reference_id, })) } } @@ -1472,7 +1292,7 @@ impl behaviour::Conversion for PaymentIntent { last_synced, setup_future_usage, client_secret, - active_attempt, + active_attempt_id, order_details, allowed_payment_method_types, connector_metadata, @@ -1501,6 +1321,7 @@ impl behaviour::Conversion for PaymentIntent { customer_present, routing_algorithm_id, payment_link_config, + platform_merchant_id, } = self; Ok(DieselPaymentIntent { skip_external_tax_calculation: Some(amount_details.get_external_tax_action_as_bool()), @@ -1518,11 +1339,25 @@ impl behaviour::Conversion for PaymentIntent { created_at, modified_at, last_synced, - setup_future_usage, + setup_future_usage: Some(setup_future_usage), client_secret, - active_attempt_id: active_attempt.get_id(), - order_details, - allowed_payment_method_types, + active_attempt_id, + order_details: order_details.map(|order_details| { + order_details + .into_iter() + .map(|order_detail| Secret::new(order_detail.expose())) + .collect::>() + }), + allowed_payment_method_types: allowed_payment_method_types + .map(|allowed_payment_method_types| { + allowed_payment_method_types + .encode_to_value() + .change_context(ValidationError::InvalidValue { + message: "Failed to serialize allowed_payment_method_types".to_string(), + }) + }) + .transpose()? + .map(Secret::new), connector_metadata, feature_metadata, attempt_count, @@ -1531,7 +1366,7 @@ impl behaviour::Conversion for PaymentIntent { payment_link_id, updated_by, - request_incremental_authorization, + request_incremental_authorization: Some(request_incremental_authorization), authorization_count, session_expiry, request_external_three_ds_authentication: Some( @@ -1541,9 +1376,9 @@ impl behaviour::Conversion for PaymentIntent { customer_details: customer_details.map(Encryption::from), billing_address: billing_address.map(Encryption::from), shipping_address: shipping_address.map(Encryption::from), - capture_method, + capture_method: Some(capture_method), id, - authentication_type, + authentication_type: Some(authentication_type), prerouting_algorithm, merchant_reference_id, surcharge_amount: amount_details.surcharge_amount, @@ -1556,6 +1391,9 @@ impl behaviour::Conversion for PaymentIntent { customer_present: Some(customer_present.as_bool()), payment_link_config, routing_algorithm_id, + psd2_sca_exemption_type: None, + platform_merchant_id, + split_payments: None, }) } async fn convert_back( @@ -1568,17 +1406,25 @@ impl behaviour::Conversion for PaymentIntent { Self: Sized, { async { - let inner_decrypt = |inner| async { - crypto_operation( - state, - type_name!(Self::DstType), - CryptoOperation::DecryptOptional(inner), - key_manager_identifier.clone(), - key.peek(), - ) - .await - .and_then(|val| val.try_into_optionaloperation()) - }; + let decrypted_data = crypto_operation( + state, + type_name!(Self::DstType), + CryptoOperation::BatchDecrypt(super::EncryptedPaymentIntent::to_encryptable( + super::EncryptedPaymentIntent { + billing_address: storage_model.billing_address, + shipping_address: storage_model.shipping_address, + customer_details: storage_model.customer_details, + }, + )), + key_manager_identifier, + key.peek(), + ) + .await + .and_then(|val| val.try_into_batchoperation())?; + + let data = super::EncryptedPaymentIntent::from_encryptable(decrypted_data) + .change_context(common_utils::errors::CryptoError::DecodingFailed) + .attach_printable("Invalid batch operation data")?; let amount_details = super::AmountDetails { order_amount: storage_model.amount, @@ -1587,14 +1433,39 @@ impl behaviour::Conversion for PaymentIntent { tax_on_surcharge: storage_model.tax_on_surcharge, shipping_cost: storage_model.shipping_cost, tax_details: storage_model.tax_details, - skip_external_tax_calculation: super::TaxCalculationOverride::from( + skip_external_tax_calculation: common_enums::TaxCalculationOverride::from( storage_model.skip_external_tax_calculation, ), - skip_surcharge_calculation: super::SurchargeCalculationOverride::from( + skip_surcharge_calculation: common_enums::SurchargeCalculationOverride::from( storage_model.surcharge_applicable, ), + amount_captured: storage_model.amount_captured, }; + let billing_address = data + .billing_address + .map(|billing| { + billing.deserialize_inner_value(|value| value.parse_value("Address")) + }) + .transpose() + .change_context(common_utils::errors::CryptoError::DecodingFailed) + .attach_printable("Error while deserializing Address")?; + + let shipping_address = data + .shipping_address + .map(|shipping| { + shipping.deserialize_inner_value(|value| value.parse_value("Address")) + }) + .transpose() + .change_context(common_utils::errors::CryptoError::DecodingFailed) + .attach_printable("Error while deserializing Address")?; + let allowed_payment_method_types = storage_model + .allowed_payment_method_types + .map(|allowed_payment_method_types| { + allowed_payment_method_types.parse_value("Vec") + }) + .transpose() + .change_context(common_utils::errors::CryptoError::DecodingFailed)?; Ok::>(Self { merchant_id: storage_model.merchant_id, status: storage_model.status, @@ -1608,11 +1479,16 @@ impl behaviour::Conversion for PaymentIntent { created_at: storage_model.created_at, modified_at: storage_model.modified_at, last_synced: storage_model.last_synced, - setup_future_usage: storage_model.setup_future_usage, + setup_future_usage: storage_model.setup_future_usage.unwrap_or_default(), client_secret: storage_model.client_secret, - active_attempt: RemoteStorageObject::ForeignID(storage_model.active_attempt_id), - order_details: storage_model.order_details, - allowed_payment_method_types: storage_model.allowed_payment_method_types, + active_attempt_id: storage_model.active_attempt_id, + order_details: storage_model.order_details.map(|order_details| { + order_details + .into_iter() + .map(|order_detail| Secret::new(order_detail.expose())) + .collect::>() + }), + allowed_payment_method_types, connector_metadata: storage_model.connector_metadata, feature_metadata: storage_model.feature_metadata, attempt_count: storage_model.attempt_count, @@ -1620,43 +1496,30 @@ impl behaviour::Conversion for PaymentIntent { frm_merchant_decision: storage_model.frm_merchant_decision, payment_link_id: storage_model.payment_link_id, updated_by: storage_model.updated_by, - request_incremental_authorization: storage_model.request_incremental_authorization, + request_incremental_authorization: storage_model + .request_incremental_authorization + .unwrap_or_default(), authorization_count: storage_model.authorization_count, session_expiry: storage_model.session_expiry, - request_external_three_ds_authentication: - common_enums::External3dsAuthenticationRequest::from( - storage_model.request_external_three_ds_authentication, - ), + request_external_three_ds_authentication: storage_model + .request_external_three_ds_authentication + .into(), frm_metadata: storage_model.frm_metadata, - customer_details: storage_model - .customer_details - .async_lift(inner_decrypt) - .await?, - billing_address: storage_model - .billing_address - .async_lift(inner_decrypt) - .await?, - shipping_address: storage_model - .shipping_address - .async_lift(inner_decrypt) - .await?, - capture_method: storage_model.capture_method, + customer_details: data.customer_details, + billing_address, + shipping_address, + capture_method: storage_model.capture_method.unwrap_or_default(), id: storage_model.id, merchant_reference_id: storage_model.merchant_reference_id, organization_id: storage_model.organization_id, - authentication_type: storage_model.authentication_type, + authentication_type: storage_model.authentication_type.unwrap_or_default(), prerouting_algorithm: storage_model.prerouting_algorithm, - enable_payment_link: common_enums::EnablePaymentLinkRequest::from( - storage_model.enable_payment_link, - ), - apply_mit_exemption: common_enums::MitExemptionRequest::from( - storage_model.apply_mit_exemption, - ), - customer_present: common_enums::PresenceOfCustomerDuringPayment::from( - storage_model.customer_present, - ), + enable_payment_link: storage_model.enable_payment_link.into(), + apply_mit_exemption: storage_model.apply_mit_exemption.into(), + customer_present: storage_model.customer_present.into(), payment_link_config: storage_model.payment_link_config, routing_algorithm_id: storage_model.routing_algorithm_id, + platform_merchant_id: storage_model.platform_merchant_id, }) } .await @@ -1684,11 +1547,21 @@ impl behaviour::Conversion for PaymentIntent { created_at: self.created_at, modified_at: self.modified_at, last_synced: self.last_synced, - setup_future_usage: self.setup_future_usage, + setup_future_usage: Some(self.setup_future_usage), client_secret: self.client_secret, - active_attempt_id: self.active_attempt.get_id(), + active_attempt_id: self.active_attempt_id, order_details: self.order_details, - allowed_payment_method_types: self.allowed_payment_method_types, + allowed_payment_method_types: self + .allowed_payment_method_types + .map(|allowed_payment_method_types| { + allowed_payment_method_types + .encode_to_value() + .change_context(ValidationError::InvalidValue { + message: "Failed to serialize allowed_payment_method_types".to_string(), + }) + }) + .transpose()? + .map(Secret::new), connector_metadata: self.connector_metadata, feature_metadata: self.feature_metadata, attempt_count: self.attempt_count, @@ -1697,7 +1570,7 @@ impl behaviour::Conversion for PaymentIntent { payment_link_id: self.payment_link_id, updated_by: self.updated_by, - request_incremental_authorization: self.request_incremental_authorization, + request_incremental_authorization: Some(self.request_incremental_authorization), authorization_count: self.authorization_count, session_expiry: self.session_expiry, request_external_three_ds_authentication: Some( @@ -1707,10 +1580,10 @@ impl behaviour::Conversion for PaymentIntent { customer_details: self.customer_details.map(Encryption::from), billing_address: self.billing_address.map(Encryption::from), shipping_address: self.shipping_address.map(Encryption::from), - capture_method: self.capture_method, + capture_method: Some(self.capture_method), id: self.id, merchant_reference_id: self.merchant_reference_id, - authentication_type: self.authentication_type, + authentication_type: Some(self.authentication_type), prerouting_algorithm: self.prerouting_algorithm, surcharge_amount: amount_details.surcharge_amount, tax_on_surcharge: amount_details.tax_on_surcharge, @@ -1719,6 +1592,7 @@ impl behaviour::Conversion for PaymentIntent { tax_details: amount_details.tax_details, enable_payment_link: Some(self.enable_payment_link.as_bool()), apply_mit_exemption: Some(self.apply_mit_exemption.as_bool()), + platform_merchant_id: self.platform_merchant_id, }) } } @@ -1772,7 +1646,8 @@ impl behaviour::Conversion 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, + charges: None, + split_payments: self.split_payments, frm_metadata: self.frm_metadata, customer_details: self.customer_details.map(Encryption::from), billing_details: self.billing_details.map(Encryption::from), @@ -1783,6 +1658,8 @@ impl behaviour::Conversion for PaymentIntent { shipping_cost: self.shipping_cost, tax_details: self.tax_details, skip_external_tax_calculation: self.skip_external_tax_calculation, + psd2_sca_exemption_type: self.psd2_sca_exemption_type, + platform_merchant_id: self.platform_merchant_id, }) } @@ -1796,17 +1673,26 @@ impl behaviour::Conversion for PaymentIntent { Self: Sized, { async { - let inner_decrypt = |inner| async { - crypto_operation( - state, - type_name!(Self::DstType), - CryptoOperation::DecryptOptional(inner), - key_manager_identifier.clone(), - key.peek(), - ) - .await - .and_then(|val| val.try_into_optionaloperation()) - }; + let decrypted_data = crypto_operation( + state, + type_name!(Self::DstType), + CryptoOperation::BatchDecrypt(super::EncryptedPaymentIntent::to_encryptable( + super::EncryptedPaymentIntent { + billing_details: storage_model.billing_details, + shipping_details: storage_model.shipping_details, + customer_details: storage_model.customer_details, + }, + )), + key_manager_identifier, + key.peek(), + ) + .await + .and_then(|val| val.try_into_batchoperation())?; + + let data = super::EncryptedPaymentIntent::from_encryptable(decrypted_data) + .change_context(common_utils::errors::CryptoError::DecodingFailed) + .attach_printable("Invalid batch operation data")?; + Ok::>(Self { payment_id: storage_model.payment_id, merchant_id: storage_model.merchant_id, @@ -1850,26 +1736,19 @@ impl behaviour::Conversion for PaymentIntent { session_expiry: storage_model.session_expiry, request_external_three_ds_authentication: storage_model .request_external_three_ds_authentication, - charges: storage_model.charges, + split_payments: storage_model.split_payments, frm_metadata: storage_model.frm_metadata, shipping_cost: storage_model.shipping_cost, tax_details: storage_model.tax_details, - customer_details: storage_model - .customer_details - .async_lift(inner_decrypt) - .await?, - billing_details: storage_model - .billing_details - .async_lift(inner_decrypt) - .await?, + customer_details: data.customer_details, + billing_details: data.billing_details, merchant_order_reference_id: storage_model.merchant_order_reference_id, - shipping_details: storage_model - .shipping_details - .async_lift(inner_decrypt) - .await?, + shipping_details: data.shipping_details, is_payment_processor_token_flow: storage_model.is_payment_processor_token_flow, organization_id: storage_model.organization_id, skip_external_tax_calculation: storage_model.skip_external_tax_calculation, + psd2_sca_exemption_type: storage_model.psd2_sca_exemption_type, + platform_merchant_id: storage_model.platform_merchant_id, }) } .await @@ -1921,7 +1800,8 @@ impl behaviour::Conversion 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, + charges: None, + split_payments: self.split_payments, frm_metadata: self.frm_metadata, customer_details: self.customer_details.map(Encryption::from), billing_details: self.billing_details.map(Encryption::from), @@ -1932,6 +1812,8 @@ impl behaviour::Conversion for PaymentIntent { shipping_cost: self.shipping_cost, tax_details: self.tax_details, skip_external_tax_calculation: self.skip_external_tax_calculation, + psd2_sca_exemption_type: self.psd2_sca_exemption_type, + platform_merchant_id: self.platform_merchant_id, }) } } diff --git a/crates/hyperswitch_domain_models/src/relay.rs b/crates/hyperswitch_domain_models/src/relay.rs new file mode 100644 index 000000000000..959ac8e7f612 --- /dev/null +++ b/crates/hyperswitch_domain_models/src/relay.rs @@ -0,0 +1,275 @@ +use common_enums::enums; +use common_utils::{ + self, + errors::{CustomResult, ValidationError}, + id_type::{self, GenerateId}, + pii, + types::{keymanager, MinorUnit}, +}; +use diesel_models::relay::RelayUpdateInternal; +use error_stack::ResultExt; +use masking::{ExposeInterface, Secret}; +use serde::{self, Deserialize, Serialize}; +use time::PrimitiveDateTime; + +use crate::{router_data::ErrorResponse, router_response_types}; + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Relay { + pub id: id_type::RelayId, + pub connector_resource_id: String, + pub connector_id: id_type::MerchantConnectorAccountId, + pub profile_id: id_type::ProfileId, + pub merchant_id: id_type::MerchantId, + pub relay_type: enums::RelayType, + pub request_data: Option, + pub status: enums::RelayStatus, + pub connector_reference_id: Option, + pub error_code: Option, + pub error_message: Option, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub created_at: PrimitiveDateTime, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub modified_at: PrimitiveDateTime, + pub response_data: Option, +} + +impl Relay { + pub fn new( + relay_request: &api_models::relay::RelayRequest, + merchant_id: &id_type::MerchantId, + profile_id: &id_type::ProfileId, + ) -> Self { + let relay_id = id_type::RelayId::generate(); + Self { + id: relay_id.clone(), + connector_resource_id: relay_request.connector_resource_id.clone(), + connector_id: relay_request.connector_id.clone(), + profile_id: profile_id.clone(), + merchant_id: merchant_id.clone(), + relay_type: common_enums::RelayType::Refund, + request_data: relay_request.data.clone().map(From::from), + status: common_enums::RelayStatus::Created, + connector_reference_id: None, + error_code: None, + error_message: None, + created_at: common_utils::date_time::now(), + modified_at: common_utils::date_time::now(), + response_data: None, + } + } +} + +impl From for RelayData { + fn from(relay: api_models::relay::RelayData) -> Self { + match relay { + api_models::relay::RelayData::Refund(relay_refund_request) => { + Self::Refund(RelayRefundData { + amount: relay_refund_request.amount, + currency: relay_refund_request.currency, + reason: relay_refund_request.reason, + }) + } + } + } +} + +impl RelayUpdate { + pub fn from( + response: Result, + ) -> Self { + match response { + Err(error) => Self::ErrorUpdate { + error_code: error.code, + error_message: error.message, + status: common_enums::RelayStatus::Failure, + }, + Ok(response) => Self::StatusUpdate { + connector_reference_id: Some(response.connector_refund_id), + status: common_enums::RelayStatus::from(response.refund_status), + }, + } + } +} + +impl From for api_models::relay::RelayResponse { + fn from(value: Relay) -> Self { + let error = value + .error_code + .zip(value.error_message) + .map( + |(error_code, error_message)| api_models::relay::RelayError { + code: error_code, + message: error_message, + }, + ); + + let data = value.request_data.map(|relay_data| match relay_data { + RelayData::Refund(relay_refund_request) => { + api_models::relay::RelayData::Refund(api_models::relay::RelayRefundRequest { + amount: relay_refund_request.amount, + currency: relay_refund_request.currency, + reason: relay_refund_request.reason, + }) + } + }); + Self { + id: value.id, + status: value.status, + error, + connector_resource_id: value.connector_resource_id, + connector_id: value.connector_id, + profile_id: value.profile_id, + relay_type: value.relay_type, + data, + connector_reference_id: value.connector_reference_id, + } + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "snake_case", untagged)] +pub enum RelayData { + Refund(RelayRefundData), +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct RelayRefundData { + pub amount: MinorUnit, + pub currency: enums::Currency, + pub reason: Option, +} + +#[derive(Debug)] +pub enum RelayUpdate { + ErrorUpdate { + error_code: String, + error_message: String, + status: enums::RelayStatus, + }, + StatusUpdate { + connector_reference_id: Option, + status: common_enums::RelayStatus, + }, +} + +impl From for RelayUpdateInternal { + fn from(value: RelayUpdate) -> Self { + match value { + RelayUpdate::ErrorUpdate { + error_code, + error_message, + status, + } => Self { + error_code: Some(error_code), + error_message: Some(error_message), + connector_reference_id: None, + status: Some(status), + modified_at: common_utils::date_time::now(), + }, + RelayUpdate::StatusUpdate { + connector_reference_id, + status, + } => Self { + connector_reference_id, + status: Some(status), + error_code: None, + error_message: None, + modified_at: common_utils::date_time::now(), + }, + } + } +} + +#[async_trait::async_trait] +impl super::behaviour::Conversion for Relay { + type DstType = diesel_models::relay::Relay; + type NewDstType = diesel_models::relay::RelayNew; + + async fn convert(self) -> CustomResult { + Ok(diesel_models::relay::Relay { + id: self.id, + connector_resource_id: self.connector_resource_id, + connector_id: self.connector_id, + profile_id: self.profile_id, + merchant_id: self.merchant_id, + relay_type: self.relay_type, + request_data: self + .request_data + .map(|data| { + serde_json::to_value(data).change_context(ValidationError::InvalidValue { + message: "Failed while decrypting business profile data".to_string(), + }) + }) + .transpose()? + .map(Secret::new), + status: self.status, + connector_reference_id: self.connector_reference_id, + error_code: self.error_code, + error_message: self.error_message, + created_at: self.created_at, + modified_at: self.modified_at, + response_data: self.response_data, + }) + } + + async fn convert_back( + _state: &keymanager::KeyManagerState, + item: Self::DstType, + _key: &Secret>, + _key_manager_identifier: keymanager::Identifier, + ) -> CustomResult { + Ok(Self { + id: item.id, + connector_resource_id: item.connector_resource_id, + connector_id: item.connector_id, + profile_id: item.profile_id, + merchant_id: item.merchant_id, + relay_type: enums::RelayType::Refund, + request_data: item + .request_data + .map(|data| { + serde_json::from_value(data.expose()).change_context( + ValidationError::InvalidValue { + message: "Failed while decrypting business profile data".to_string(), + }, + ) + }) + .transpose()?, + status: item.status, + connector_reference_id: item.connector_reference_id, + error_code: item.error_code, + error_message: item.error_message, + created_at: item.created_at, + modified_at: item.modified_at, + response_data: item.response_data, + }) + } + + async fn construct_new(self) -> CustomResult { + Ok(diesel_models::relay::RelayNew { + id: self.id, + connector_resource_id: self.connector_resource_id, + connector_id: self.connector_id, + profile_id: self.profile_id, + merchant_id: self.merchant_id, + relay_type: self.relay_type, + request_data: self + .request_data + .map(|data| { + serde_json::to_value(data).change_context(ValidationError::InvalidValue { + message: "Failed while decrypting business profile data".to_string(), + }) + }) + .transpose()? + .map(Secret::new), + status: self.status, + connector_reference_id: self.connector_reference_id, + error_code: self.error_code, + error_message: self.error_message, + created_at: self.created_at, + modified_at: self.modified_at, + response_data: self.response_data, + }) + } +} diff --git a/crates/hyperswitch_domain_models/src/router_data.rs b/crates/hyperswitch_domain_models/src/router_data.rs index cd8ac85594e7..2970ac127ed3 100644 --- a/crates/hyperswitch_domain_models/src/router_data.rs +++ b/crates/hyperswitch_domain_models/src/router_data.rs @@ -9,7 +9,7 @@ use common_utils::{ use error_stack::ResultExt; use masking::{ExposeInterface, Secret}; -use crate::{payment_address::PaymentAddress, payment_method_data}; +use crate::{payment_address::PaymentAddress, payment_method_data, payments}; #[derive(Debug, Clone)] pub struct RouterData { @@ -26,7 +26,6 @@ pub struct RouterData { 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, @@ -83,7 +82,13 @@ pub struct RouterData { pub additional_merchant_data: Option, - pub header_payload: Option, + pub header_payload: Option, + + pub connector_mandate_request_reference_id: Option, + + pub authentication_id: Option, + /// Contains the type of sca exemption required for the transaction + pub psd2_sca_exemption_type: Option, } // Different patterns of authentication. @@ -220,6 +225,7 @@ pub struct AccessToken { pub enum PaymentMethodToken { Token(Secret), ApplePayDecrypt(Box), + PazeDecrypt(Box), } #[derive(Debug, Clone, serde::Deserialize)] @@ -241,12 +247,75 @@ pub struct ApplePayCryptogramData { pub eci_indicator: Option, } +#[derive(Debug, Clone, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PazeDecryptedData { + pub client_id: Secret, + pub profile_id: String, + pub token: PazeToken, + pub payment_card_network: common_enums::enums::CardNetwork, + pub dynamic_data: Vec, + pub billing_address: PazeAddress, + pub consumer: PazeConsumer, + pub eci: Option, +} + +#[derive(Debug, Clone, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PazeToken { + pub payment_token: cards::CardNumber, + pub token_expiration_month: Secret, + pub token_expiration_year: Secret, + pub payment_account_reference: Secret, +} + +#[derive(Debug, Clone, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PazeDynamicData { + pub dynamic_data_value: Option>, + pub dynamic_data_type: Option, + pub dynamic_data_expiration: Option, +} + +#[derive(Debug, Clone, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PazeAddress { + pub name: Option>, + pub line1: Option>, + pub line2: Option>, + pub line3: Option>, + pub city: Option>, + pub state: Option>, + pub zip: Option>, + pub country_code: Option, +} + +#[derive(Debug, Clone, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PazeConsumer { + // This is consumer data not customer data. + pub first_name: Option>, + pub last_name: Option>, + pub full_name: Secret, + pub email_address: common_utils::pii::Email, + pub mobile_number: Option, + pub country_code: Option, + pub language_code: Option, +} + +#[derive(Debug, Clone, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct PazePhoneNumber { + pub country_code: Secret, + pub phone_number: Secret, +} + #[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, - pub mandate_metadata: Option, + pub mandate_metadata: Option, } #[derive(Debug, Clone)] @@ -323,3 +392,680 @@ impl ErrorResponse { } } } + +#[cfg(feature = "v2")] +use crate::{ + payments::{ + payment_attempt::{ErrorDetails, PaymentAttemptUpdate}, + payment_intent::PaymentIntentUpdate, + }, + router_flow_types, router_request_types, router_response_types, +}; + +/// Get updatable trakcer objects of payment intent and payment attempt +#[cfg(feature = "v2")] +pub trait TrackerPostUpdateObjects { + fn get_payment_intent_update( + &self, + payment_data: &D, + storage_scheme: common_enums::MerchantStorageScheme, + ) -> PaymentIntentUpdate; + + fn get_payment_attempt_update( + &self, + payment_data: &D, + storage_scheme: common_enums::MerchantStorageScheme, + ) -> PaymentAttemptUpdate; + + /// Get the amount that can be captured for the payment + fn get_amount_capturable(&self, payment_data: &D) -> Option; + + /// Get the amount that has been captured for the payment + fn get_captured_amount(&self, payment_data: &D) -> Option; + + /// Get the attempt status based on parameters like captured amount and amount capturable + fn get_attempt_status_for_db_update( + &self, + payment_data: &D, + ) -> common_enums::enums::AttemptStatus; +} + +#[cfg(feature = "v2")] +impl + TrackerPostUpdateObjects< + router_flow_types::Authorize, + router_request_types::PaymentsAuthorizeData, + payments::PaymentConfirmData, + > + for RouterData< + router_flow_types::Authorize, + router_request_types::PaymentsAuthorizeData, + router_response_types::PaymentsResponseData, + > +{ + fn get_payment_intent_update( + &self, + payment_data: &payments::PaymentConfirmData, + storage_scheme: common_enums::MerchantStorageScheme, + ) -> PaymentIntentUpdate { + let amount_captured = self.get_captured_amount(payment_data); + match self.response { + Ok(ref _response) => PaymentIntentUpdate::ConfirmIntentPostUpdate { + status: common_enums::IntentStatus::from( + self.get_attempt_status_for_db_update(payment_data), + ), + amount_captured, + updated_by: storage_scheme.to_string(), + }, + Err(ref error) => PaymentIntentUpdate::ConfirmIntentPostUpdate { + status: error + .attempt_status + .map(common_enums::IntentStatus::from) + .unwrap_or(common_enums::IntentStatus::Failed), + amount_captured, + updated_by: storage_scheme.to_string(), + }, + } + } + + fn get_payment_attempt_update( + &self, + payment_data: &payments::PaymentConfirmData, + storage_scheme: common_enums::MerchantStorageScheme, + ) -> PaymentAttemptUpdate { + let amount_capturable = self.get_amount_capturable(payment_data); + + match self.response { + Ok(ref response_router_data) => match response_router_data { + router_response_types::PaymentsResponseData::TransactionResponse { + resource_id, + redirection_data, + mandate_reference, + connector_metadata, + network_txn_id, + connector_response_reference_id, + incremental_authorization_allowed, + charge_id, + } => { + let attempt_status = self.get_attempt_status_for_db_update(payment_data); + + let connector_payment_id = match resource_id { + router_request_types::ResponseId::NoResponseId => None, + router_request_types::ResponseId::ConnectorTransactionId(id) + | router_request_types::ResponseId::EncodedData(id) => Some(id.to_owned()), + }; + + PaymentAttemptUpdate::ConfirmIntentResponse { + status: attempt_status, + connector_payment_id, + updated_by: storage_scheme.to_string(), + redirection_data: *redirection_data.clone(), + amount_capturable, + connector_metadata: connector_metadata.clone().map(Secret::new), + } + } + router_response_types::PaymentsResponseData::MultipleCaptureResponse { .. } => { + todo!() + } + router_response_types::PaymentsResponseData::SessionResponse { .. } => todo!(), + router_response_types::PaymentsResponseData::SessionTokenResponse { .. } => todo!(), + router_response_types::PaymentsResponseData::TransactionUnresolvedResponse { + .. + } => todo!(), + router_response_types::PaymentsResponseData::TokenizationResponse { .. } => todo!(), + router_response_types::PaymentsResponseData::ConnectorCustomerResponse { + .. + } => todo!(), + router_response_types::PaymentsResponseData::ThreeDSEnrollmentResponse { + .. + } => todo!(), + router_response_types::PaymentsResponseData::PreProcessingResponse { .. } => { + todo!() + } + router_response_types::PaymentsResponseData::IncrementalAuthorizationResponse { + .. + } => todo!(), + router_response_types::PaymentsResponseData::PostProcessingResponse { .. } => { + todo!() + } + router_response_types::PaymentsResponseData::SessionUpdateResponse { .. } => { + todo!() + } + }, + Err(ref error_response) => { + let ErrorResponse { + code, + message, + reason, + status_code: _, + attempt_status, + connector_transaction_id, + } = error_response.clone(); + let attempt_status = attempt_status.unwrap_or(self.status); + + let error_details = ErrorDetails { + code, + message, + reason, + unified_code: None, + unified_message: None, + }; + + PaymentAttemptUpdate::ErrorUpdate { + status: attempt_status, + error: error_details, + amount_capturable, + connector_payment_id: connector_transaction_id, + updated_by: storage_scheme.to_string(), + } + } + } + } + + fn get_attempt_status_for_db_update( + &self, + _payment_data: &payments::PaymentConfirmData, + ) -> common_enums::AttemptStatus { + // For this step, consider whatever status was given by the connector module + // We do not need to check for amount captured or amount capturable here because we are authorizing the whole amount + self.status + } + + fn get_amount_capturable( + &self, + payment_data: &payments::PaymentConfirmData, + ) -> Option { + // Based on the status of the response, we can determine the amount capturable + let intent_status = common_enums::IntentStatus::from(self.status); + match intent_status { + // If the status is already succeeded / failed we cannot capture any more amount + // So set the amount capturable to zero + common_enums::IntentStatus::Succeeded + | common_enums::IntentStatus::Failed + | common_enums::IntentStatus::Cancelled => Some(MinorUnit::zero()), + // For these statuses, update the capturable amount when it reaches terminal / capturable state + common_enums::IntentStatus::RequiresCustomerAction + | common_enums::IntentStatus::RequiresMerchantAction + | common_enums::IntentStatus::Processing => None, + // Invalid states for this flow + common_enums::IntentStatus::RequiresPaymentMethod + | common_enums::IntentStatus::RequiresConfirmation => None, + // If status is requires capture, get the total amount that can be captured + // This is in cases where the capture method will be `manual` or `manual_multiple` + // We do not need to handle the case where amount_to_capture is provided here as it cannot be passed in authroize flow + common_enums::IntentStatus::RequiresCapture => { + let total_amount = payment_data.payment_attempt.amount_details.get_net_amount(); + Some(total_amount) + } + // Invalid statues for this flow, after doing authorization this state is invalid + common_enums::IntentStatus::PartiallyCaptured + | common_enums::IntentStatus::PartiallyCapturedAndCapturable => None, + } + } + + fn get_captured_amount( + &self, + payment_data: &payments::PaymentConfirmData, + ) -> Option { + // Based on the status of the response, we can determine the amount that was captured + let intent_status = common_enums::IntentStatus::from(self.status); + match intent_status { + // If the status is succeeded then we have captured the whole amount + // we need not check for `amount_to_capture` here because passing `amount_to_capture` when authorizing is not supported + common_enums::IntentStatus::Succeeded => { + let total_amount = payment_data.payment_attempt.amount_details.get_net_amount(); + Some(total_amount) + } + // No amount is captured + common_enums::IntentStatus::Cancelled | common_enums::IntentStatus::Failed => { + Some(MinorUnit::zero()) + } + // For these statuses, update the amount captured when it reaches terminal state + common_enums::IntentStatus::RequiresCustomerAction + | common_enums::IntentStatus::RequiresMerchantAction + | common_enums::IntentStatus::Processing => None, + // Invalid states for this flow + common_enums::IntentStatus::RequiresPaymentMethod + | common_enums::IntentStatus::RequiresConfirmation => None, + // No amount has been captured yet + common_enums::IntentStatus::RequiresCapture => Some(MinorUnit::zero()), + // Invalid statues for this flow + common_enums::IntentStatus::PartiallyCaptured + | common_enums::IntentStatus::PartiallyCapturedAndCapturable => None, + } + } +} + +#[cfg(feature = "v2")] +impl + TrackerPostUpdateObjects< + router_flow_types::Capture, + router_request_types::PaymentsCaptureData, + payments::PaymentCaptureData, + > + for RouterData< + router_flow_types::Capture, + router_request_types::PaymentsCaptureData, + router_response_types::PaymentsResponseData, + > +{ + fn get_payment_intent_update( + &self, + payment_data: &payments::PaymentCaptureData, + storage_scheme: common_enums::MerchantStorageScheme, + ) -> PaymentIntentUpdate { + let amount_captured = self.get_captured_amount(payment_data); + match self.response { + Ok(ref _response) => PaymentIntentUpdate::CaptureUpdate { + status: common_enums::IntentStatus::from( + self.get_attempt_status_for_db_update(payment_data), + ), + amount_captured, + updated_by: storage_scheme.to_string(), + }, + Err(ref error) => PaymentIntentUpdate::CaptureUpdate { + status: error + .attempt_status + .map(common_enums::IntentStatus::from) + .unwrap_or(common_enums::IntentStatus::Failed), + amount_captured, + updated_by: storage_scheme.to_string(), + }, + } + } + + fn get_payment_attempt_update( + &self, + payment_data: &payments::PaymentCaptureData, + storage_scheme: common_enums::MerchantStorageScheme, + ) -> PaymentAttemptUpdate { + let amount_capturable = self.get_amount_capturable(payment_data); + + match self.response { + Ok(ref response_router_data) => match response_router_data { + router_response_types::PaymentsResponseData::TransactionResponse { + resource_id, + redirection_data, + mandate_reference, + connector_metadata, + network_txn_id, + connector_response_reference_id, + incremental_authorization_allowed, + charge_id, + } => { + let attempt_status = self.status; + + PaymentAttemptUpdate::CaptureUpdate { + status: attempt_status, + amount_capturable, + updated_by: storage_scheme.to_string(), + } + } + router_response_types::PaymentsResponseData::MultipleCaptureResponse { .. } => { + todo!() + } + router_response_types::PaymentsResponseData::SessionResponse { .. } => todo!(), + router_response_types::PaymentsResponseData::SessionTokenResponse { .. } => todo!(), + router_response_types::PaymentsResponseData::TransactionUnresolvedResponse { + .. + } => todo!(), + router_response_types::PaymentsResponseData::TokenizationResponse { .. } => todo!(), + router_response_types::PaymentsResponseData::ConnectorCustomerResponse { + .. + } => todo!(), + router_response_types::PaymentsResponseData::ThreeDSEnrollmentResponse { + .. + } => todo!(), + router_response_types::PaymentsResponseData::PreProcessingResponse { .. } => { + todo!() + } + router_response_types::PaymentsResponseData::IncrementalAuthorizationResponse { + .. + } => todo!(), + router_response_types::PaymentsResponseData::PostProcessingResponse { .. } => { + todo!() + } + router_response_types::PaymentsResponseData::SessionUpdateResponse { .. } => { + todo!() + } + }, + Err(ref error_response) => { + let ErrorResponse { + code, + message, + reason, + status_code: _, + attempt_status, + connector_transaction_id, + } = error_response.clone(); + let attempt_status = attempt_status.unwrap_or(self.status); + + let error_details = ErrorDetails { + code, + message, + reason, + unified_code: None, + unified_message: None, + }; + + PaymentAttemptUpdate::ErrorUpdate { + status: attempt_status, + error: error_details, + amount_capturable, + connector_payment_id: connector_transaction_id, + updated_by: storage_scheme.to_string(), + } + } + } + } + + fn get_attempt_status_for_db_update( + &self, + payment_data: &payments::PaymentCaptureData, + ) -> common_enums::AttemptStatus { + match self.status { + common_enums::AttemptStatus::Charged => { + let amount_captured = self + .get_captured_amount(payment_data) + .unwrap_or(MinorUnit::zero()); + let total_amount = payment_data.payment_attempt.amount_details.get_net_amount(); + + if amount_captured == total_amount { + common_enums::AttemptStatus::Charged + } else { + common_enums::AttemptStatus::PartialCharged + } + } + _ => self.status, + } + } + + fn get_amount_capturable( + &self, + payment_data: &payments::PaymentCaptureData, + ) -> Option { + // Based on the status of the response, we can determine the amount capturable + let intent_status = common_enums::IntentStatus::from(self.status); + match intent_status { + // If the status is already succeeded / failed we cannot capture any more amount + common_enums::IntentStatus::Succeeded + | common_enums::IntentStatus::Failed + | common_enums::IntentStatus::Cancelled => Some(MinorUnit::zero()), + // For these statuses, update the capturable amount when it reaches terminal / capturable state + common_enums::IntentStatus::RequiresCustomerAction + | common_enums::IntentStatus::RequiresMerchantAction + | common_enums::IntentStatus::Processing => None, + // Invalid states for this flow + common_enums::IntentStatus::RequiresPaymentMethod + | common_enums::IntentStatus::RequiresConfirmation => None, + common_enums::IntentStatus::RequiresCapture => { + let total_amount = payment_data.payment_attempt.amount_details.get_net_amount(); + Some(total_amount) + } + // Invalid statues for this flow + common_enums::IntentStatus::PartiallyCaptured + | common_enums::IntentStatus::PartiallyCapturedAndCapturable => None, + } + } + + fn get_captured_amount( + &self, + payment_data: &payments::PaymentCaptureData, + ) -> Option { + // Based on the status of the response, we can determine the amount capturable + let intent_status = common_enums::IntentStatus::from(self.status); + match intent_status { + // If the status is succeeded then we have captured the whole amount + common_enums::IntentStatus::Succeeded => { + let amount_to_capture = payment_data + .payment_attempt + .amount_details + .get_amount_to_capture(); + + let amount_captured = amount_to_capture + .unwrap_or(payment_data.payment_attempt.amount_details.get_net_amount()); + + Some(amount_captured) + } + // No amount is captured + common_enums::IntentStatus::Cancelled | common_enums::IntentStatus::Failed => { + Some(MinorUnit::zero()) + } + common_enums::IntentStatus::RequiresCapture => { + let total_amount = payment_data.payment_attempt.amount_details.get_net_amount(); + Some(total_amount) + } + // For these statuses, update the amount captured when it reaches terminal state + common_enums::IntentStatus::RequiresCustomerAction + | common_enums::IntentStatus::RequiresMerchantAction + | common_enums::IntentStatus::Processing => None, + // Invalid states for this flow + common_enums::IntentStatus::RequiresPaymentMethod + | common_enums::IntentStatus::RequiresConfirmation => None, + // Invalid statues for this flow + common_enums::IntentStatus::PartiallyCaptured + | common_enums::IntentStatus::PartiallyCapturedAndCapturable => { + todo!() + } + } + } +} + +#[cfg(feature = "v2")] +impl + TrackerPostUpdateObjects< + router_flow_types::PSync, + router_request_types::PaymentsSyncData, + payments::PaymentStatusData, + > + for RouterData< + router_flow_types::PSync, + router_request_types::PaymentsSyncData, + router_response_types::PaymentsResponseData, + > +{ + fn get_payment_intent_update( + &self, + payment_data: &payments::PaymentStatusData, + storage_scheme: common_enums::MerchantStorageScheme, + ) -> PaymentIntentUpdate { + let amount_captured = self.get_captured_amount(payment_data); + match self.response { + Ok(ref _response) => PaymentIntentUpdate::SyncUpdate { + status: common_enums::IntentStatus::from( + self.get_attempt_status_for_db_update(payment_data), + ), + amount_captured, + updated_by: storage_scheme.to_string(), + }, + Err(ref error) => PaymentIntentUpdate::SyncUpdate { + status: error + .attempt_status + .map(common_enums::IntentStatus::from) + .unwrap_or(common_enums::IntentStatus::Failed), + amount_captured, + updated_by: storage_scheme.to_string(), + }, + } + } + + fn get_payment_attempt_update( + &self, + payment_data: &payments::PaymentStatusData, + storage_scheme: common_enums::MerchantStorageScheme, + ) -> PaymentAttemptUpdate { + let amount_capturable = self.get_amount_capturable(payment_data); + + match self.response { + Ok(ref response_router_data) => match response_router_data { + router_response_types::PaymentsResponseData::TransactionResponse { + resource_id, + redirection_data, + mandate_reference, + connector_metadata, + network_txn_id, + connector_response_reference_id, + incremental_authorization_allowed, + charge_id, + } => { + let attempt_status = self.get_attempt_status_for_db_update(payment_data); + + PaymentAttemptUpdate::SyncUpdate { + status: attempt_status, + amount_capturable, + updated_by: storage_scheme.to_string(), + } + } + router_response_types::PaymentsResponseData::MultipleCaptureResponse { .. } => { + todo!() + } + router_response_types::PaymentsResponseData::SessionResponse { .. } => todo!(), + router_response_types::PaymentsResponseData::SessionTokenResponse { .. } => todo!(), + router_response_types::PaymentsResponseData::TransactionUnresolvedResponse { + .. + } => todo!(), + router_response_types::PaymentsResponseData::TokenizationResponse { .. } => todo!(), + router_response_types::PaymentsResponseData::ConnectorCustomerResponse { + .. + } => todo!(), + router_response_types::PaymentsResponseData::ThreeDSEnrollmentResponse { + .. + } => todo!(), + router_response_types::PaymentsResponseData::PreProcessingResponse { .. } => { + todo!() + } + router_response_types::PaymentsResponseData::IncrementalAuthorizationResponse { + .. + } => todo!(), + router_response_types::PaymentsResponseData::PostProcessingResponse { .. } => { + todo!() + } + router_response_types::PaymentsResponseData::SessionUpdateResponse { .. } => { + todo!() + } + }, + Err(ref error_response) => { + let ErrorResponse { + code, + message, + reason, + status_code: _, + attempt_status, + connector_transaction_id, + } = error_response.clone(); + let attempt_status = attempt_status.unwrap_or(self.status); + + let error_details = ErrorDetails { + code, + message, + reason, + unified_code: None, + unified_message: None, + }; + + PaymentAttemptUpdate::ErrorUpdate { + status: attempt_status, + error: error_details, + amount_capturable, + connector_payment_id: connector_transaction_id, + updated_by: storage_scheme.to_string(), + } + } + } + } + + fn get_attempt_status_for_db_update( + &self, + payment_data: &payments::PaymentStatusData, + ) -> common_enums::AttemptStatus { + match self.status { + common_enums::AttemptStatus::Charged => { + let amount_captured = self + .get_captured_amount(payment_data) + .unwrap_or(MinorUnit::zero()); + + let total_amount = payment_data + .payment_attempt + .as_ref() + .map(|attempt| attempt.amount_details.get_net_amount()) + .unwrap_or(MinorUnit::zero()); + + if amount_captured == total_amount { + common_enums::AttemptStatus::Charged + } else { + common_enums::AttemptStatus::PartialCharged + } + } + _ => self.status, + } + } + + fn get_amount_capturable( + &self, + payment_data: &payments::PaymentStatusData, + ) -> Option { + let payment_attempt = payment_data.payment_attempt.as_ref()?; + + // Based on the status of the response, we can determine the amount capturable + let intent_status = common_enums::IntentStatus::from(self.status); + match intent_status { + // If the status is already succeeded / failed we cannot capture any more amount + common_enums::IntentStatus::Succeeded + | common_enums::IntentStatus::Failed + | common_enums::IntentStatus::Cancelled => Some(MinorUnit::zero()), + // For these statuses, update the capturable amount when it reaches terminal / capturable state + common_enums::IntentStatus::RequiresCustomerAction + | common_enums::IntentStatus::RequiresMerchantAction + | common_enums::IntentStatus::Processing => None, + // Invalid states for this flow + common_enums::IntentStatus::RequiresPaymentMethod + | common_enums::IntentStatus::RequiresConfirmation => None, + common_enums::IntentStatus::RequiresCapture => { + let total_amount = payment_attempt.amount_details.get_net_amount(); + Some(total_amount) + } + // Invalid statues for this flow + common_enums::IntentStatus::PartiallyCaptured + | common_enums::IntentStatus::PartiallyCapturedAndCapturable => None, + } + } + + fn get_captured_amount( + &self, + payment_data: &payments::PaymentStatusData, + ) -> Option { + let payment_attempt = payment_data.payment_attempt.as_ref()?; + + // Based on the status of the response, we can determine the amount capturable + let intent_status = common_enums::IntentStatus::from(self.status); + match intent_status { + // If the status is succeeded then we have captured the whole amount or amount_to_capture + common_enums::IntentStatus::Succeeded => { + let amount_to_capture = payment_attempt.amount_details.get_amount_to_capture(); + + let amount_captured = + amount_to_capture.unwrap_or(payment_attempt.amount_details.get_net_amount()); + + Some(amount_captured) + } + // No amount is captured + common_enums::IntentStatus::Cancelled | common_enums::IntentStatus::Failed => { + Some(MinorUnit::zero()) + } + // For these statuses, update the amount captured when it reaches terminal state + common_enums::IntentStatus::RequiresCustomerAction + | common_enums::IntentStatus::RequiresMerchantAction + | common_enums::IntentStatus::Processing => None, + // Invalid states for this flow + common_enums::IntentStatus::RequiresPaymentMethod + | common_enums::IntentStatus::RequiresConfirmation => None, + common_enums::IntentStatus::RequiresCapture => { + let total_amount = payment_attempt.amount_details.get_net_amount(); + Some(total_amount) + } + // Invalid statues for this flow + common_enums::IntentStatus::PartiallyCaptured + | common_enums::IntentStatus::PartiallyCapturedAndCapturable => None, + } + } +} diff --git a/crates/hyperswitch_domain_models/src/router_data_v2.rs b/crates/hyperswitch_domain_models/src/router_data_v2.rs index 9e3a7f255a2e..acc7343cb145 100644 --- a/crates/hyperswitch_domain_models/src/router_data_v2.rs +++ b/crates/hyperswitch_domain_models/src/router_data_v2.rs @@ -8,7 +8,7 @@ pub use flow_common_types::FrmFlowData; pub use flow_common_types::PayoutFlowData; pub use flow_common_types::{ AccessTokenFlowData, DisputesFlowData, ExternalAuthenticationFlowData, FilesFlowData, - MandateRevokeFlowData, PaymentFlowData, RefundFlowData, WebhookSourceVerifyData, + MandateRevokeFlowData, PaymentFlowData, RefundFlowData, UasFlowData, WebhookSourceVerifyData, }; use crate::router_data::{ConnectorAuthType, ErrorResponse}; diff --git a/crates/hyperswitch_domain_models/src/router_data_v2/flow_common_types.rs b/crates/hyperswitch_domain_models/src/router_data_v2/flow_common_types.rs index b2dea40082ac..47b2134bc19f 100644 --- a/crates/hyperswitch_domain_models/src/router_data_v2/flow_common_types.rs +++ b/crates/hyperswitch_domain_models/src/router_data_v2/flow_common_types.rs @@ -19,7 +19,6 @@ pub struct PaymentFlowData { pub status: common_enums::AttemptStatus, pub payment_method: common_enums::PaymentMethod, pub description: Option, - pub return_url: Option, pub address: PaymentAddress, pub auth_type: common_enums::AuthenticationType, pub connector_meta_data: Option, @@ -59,7 +58,6 @@ pub struct RefundFlowData { pub attempt_id: String, pub status: common_enums::AttemptStatus, pub payment_method: common_enums::PaymentMethod, - pub return_url: Option, pub connector_meta_data: Option, pub amount_captured: Option, // minor amount for amount framework @@ -75,7 +73,6 @@ pub struct PayoutFlowData { pub merchant_id: common_utils::id_type::MerchantId, pub customer_id: Option, pub connector_customer: Option, - pub return_url: Option, pub address: PaymentAddress, pub connector_meta_data: Option, pub connector_wallets_details: Option, @@ -93,7 +90,6 @@ pub struct FrmFlowData { pub attempt_id: String, pub payment_method: common_enums::enums::PaymentMethod, pub connector_request_reference_id: String, - pub return_url: Option, pub auth_type: common_enums::enums::AuthenticationType, pub connector_wallets_details: Option, pub connector_meta_data: Option, @@ -115,7 +111,6 @@ pub struct DisputesFlowData { pub payment_id: String, pub attempt_id: String, pub payment_method: common_enums::enums::PaymentMethod, - pub return_url: Option, pub connector_meta_data: Option, pub amount_captured: Option, // minor amount for amount framework @@ -145,7 +140,12 @@ pub struct FilesFlowData { pub merchant_id: common_utils::id_type::MerchantId, pub payment_id: String, pub attempt_id: String, - pub return_url: Option, pub connector_meta_data: Option, pub connector_request_reference_id: String, } + +#[derive(Debug, Clone)] +pub struct UasFlowData { + pub authenticate_by: String, + pub source_authentication_id: String, +} diff --git a/crates/hyperswitch_domain_models/src/router_flow_types.rs b/crates/hyperswitch_domain_models/src/router_flow_types.rs index 6cdde87772b0..bdd7691b681a 100644 --- a/crates/hyperswitch_domain_models/src/router_flow_types.rs +++ b/crates/hyperswitch_domain_models/src/router_flow_types.rs @@ -6,6 +6,7 @@ pub mod mandate_revoke; pub mod payments; pub mod payouts; pub mod refunds; +pub mod unified_authentication_service; pub mod webhooks; pub use access_token_auth::*; @@ -15,4 +16,5 @@ pub use fraud_check::*; pub use payments::*; pub use payouts::*; pub use refunds::*; +pub use unified_authentication_service::*; pub use webhooks::*; diff --git a/crates/hyperswitch_domain_models/src/router_flow_types/payments.rs b/crates/hyperswitch_domain_models/src/router_flow_types/payments.rs index a0e6adc855a9..bbe1a65de860 100644 --- a/crates/hyperswitch_domain_models/src/router_flow_types/payments.rs +++ b/crates/hyperswitch_domain_models/src/router_flow_types/payments.rs @@ -55,3 +55,15 @@ pub struct CalculateTax; #[derive(Debug, Clone)] pub struct SdkSessionUpdate; + +#[derive(Debug, Clone)] +pub struct PaymentCreateIntent; + +#[derive(Debug, Clone)] +pub struct PaymentGetIntent; + +#[derive(Debug, Clone)] +pub struct PaymentUpdateIntent; + +#[derive(Debug, Clone)] +pub struct PostSessionTokens; diff --git a/crates/hyperswitch_domain_models/src/router_flow_types/unified_authentication_service.rs b/crates/hyperswitch_domain_models/src/router_flow_types/unified_authentication_service.rs new file mode 100644 index 000000000000..329c18b741ed --- /dev/null +++ b/crates/hyperswitch_domain_models/src/router_flow_types/unified_authentication_service.rs @@ -0,0 +1,5 @@ +#[derive(Debug, Clone)] +pub struct PreAuthenticate; + +#[derive(Debug, Clone)] +pub struct PostAuthenticate; diff --git a/crates/hyperswitch_domain_models/src/router_flow_types/webhooks.rs b/crates/hyperswitch_domain_models/src/router_flow_types/webhooks.rs index 6f28a7f7b35c..c7f155fa1667 100644 --- a/crates/hyperswitch_domain_models/src/router_flow_types/webhooks.rs +++ b/crates/hyperswitch_domain_models/src/router_flow_types/webhooks.rs @@ -1,2 +1,21 @@ +use serde::Serialize; + #[derive(Clone, Debug)] pub struct VerifyWebhookSource; + +#[derive(Debug, Clone, Serialize)] +pub struct ConnectorMandateDetails { + pub connector_mandate_id: masking::Secret, +} + +#[derive(Debug, Clone, Serialize)] +pub struct ConnectorNetworkTxnId(masking::Secret); + +impl ConnectorNetworkTxnId { + pub fn new(txn_id: masking::Secret) -> Self { + Self(txn_id) + } + pub fn get_id(&self) -> &masking::Secret { + &self.0 + } +} diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index 5a0d599dcbc9..aa9fb2b5f1f0 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -1,13 +1,9 @@ pub mod authentication; pub mod fraud_check; -use api_models::payments::{Address, RequestSurchargeDetails}; -use common_utils::{ - consts, errors, - ext_traits::OptionExt, - id_type, pii, - types::{self as common_types, MinorUnit}, -}; -use diesel_models::enums as storage_enums; +pub mod unified_authentication_service; +use api_models::payments::{AdditionalPaymentData, RequestSurchargeDetails}; +use common_utils::{consts, errors, ext_traits::OptionExt, id_type, pii, types::MinorUnit}; +use diesel_models::{enums as storage_enums, types::OrderDetailsWithAmount}; use error_stack::ResultExt; use masking::Secret; use serde::Serialize; @@ -15,6 +11,7 @@ use serde_with::serde_as; use super::payment_method_data::PaymentMethodData; use crate::{ + address, errors::api_error_response::ApiErrorResponse, mandates, payments, router_data::{self, RouterData}, @@ -32,6 +29,7 @@ pub struct PaymentsAuthorizeData { /// get_total_surcharge_amount() // returns surcharge_amount + tax_on_surcharge_amount /// ``` pub amount: i64, + pub order_tax_amount: Option, pub email: Option, pub customer_name: Option>, pub currency: storage_enums::Currency, @@ -49,7 +47,7 @@ pub struct PaymentsAuthorizeData { pub customer_acceptance: Option, pub setup_mandate_details: Option, pub browser_info: Option, - pub order_details: Option>, + pub order_details: Option>, pub order_category: Option, pub session_token: Option, pub enrolled_for_3ds: bool, @@ -61,7 +59,7 @@ pub struct PaymentsAuthorizeData { pub request_incremental_authorization: bool, pub metadata: Option, pub authentication_data: Option, - pub charges: Option, + pub split_payments: Option, // New amount for amount frame work pub minor_amount: MinorUnit, @@ -71,6 +69,25 @@ pub struct PaymentsAuthorizeData { /// In case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference. pub merchant_order_reference_id: Option, pub integrity_object: Option, + pub shipping_cost: Option, + pub additional_payment_method_data: Option, +} + +#[derive(Debug, Clone)] +pub struct PaymentsPostSessionTokensData { + // amount here would include amount, surcharge_amount and shipping_cost + pub amount: MinorUnit, + /// original amount sent by the merchant + pub order_amount: MinorUnit, + pub currency: storage_enums::Currency, + pub capture_method: Option, + /// Merchant's identifier for the payment/invoice. This will be sent to the connector + /// if the connector provides support to accept multiple reference ids. + /// In case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference. + pub merchant_order_reference_id: Option, + pub shipping_cost: Option, + pub setup_future_usage: Option, + pub router_return_url: Option, } #[derive(Debug, Clone, PartialEq)] @@ -89,13 +106,6 @@ pub struct SyncIntegrityObject { pub currency: Option, } -#[derive(Debug, serde::Deserialize, Clone)] -pub struct PaymentCharges { - pub charge_type: api_models::enums::PaymentChargeType, - pub fees: MinorUnit, - pub transfer_account_id: String, -} - #[derive(Debug, Clone, Default)] pub struct PaymentsCaptureData { pub amount_to_capture: i64, @@ -107,6 +117,7 @@ pub struct PaymentsCaptureData { pub browser_info: Option, pub metadata: Option, // This metadata is used to store the metadata shared during the payment intent request. + pub capture_method: Option, // New amount for amount frame work pub minor_payment_amount: MinorUnit, @@ -268,7 +279,7 @@ pub struct PaymentsPreProcessingData { pub payment_method_type: Option, pub setup_mandate_details: Option, pub capture_method: Option, - pub order_details: Option>, + pub order_details: Option>, pub router_return_url: Option, pub webhook_url: Option, pub complete_authorize_url: Option, @@ -279,6 +290,7 @@ pub struct PaymentsPreProcessingData { pub mandate_id: Option, pub related_transaction_id: Option, pub redirect_response: Option, + pub metadata: Option>, // New amount for amount frame work pub minor_amount: Option, @@ -308,6 +320,7 @@ impl TryFrom for PaymentsPreProcessingData { related_transaction_id: data.related_transaction_id, redirect_response: None, enrolled_for_3ds: data.enrolled_for_3ds, + metadata: data.metadata.map(Secret::new), }) } } @@ -336,6 +349,7 @@ impl TryFrom for PaymentsPreProcessingData { related_transaction_id: None, redirect_response: data.redirect_response, enrolled_for_3ds: true, + metadata: data.connector_meta.map(Secret::new), }) } } @@ -347,7 +361,7 @@ pub struct PaymentsPostProcessingData { pub connector_transaction_id: Option, pub country: Option, pub connector_meta_data: Option, - pub header_payload: Option, + pub header_payload: Option, } impl TryFrom> @@ -421,8 +435,7 @@ pub struct PaymentsSyncData { pub payment_method_type: Option, pub currency: storage_enums::Currency, pub payment_experience: Option, - pub charges: Option, - + pub split_payments: Option, pub amount: MinorUnit, pub integrity_object: Option, } @@ -473,6 +486,9 @@ pub struct BrowserInformation { pub ip_address: Option, pub accept_header: Option, pub user_agent: Option, + pub os_type: Option, + pub os_version: Option, + pub device_model: Option, } #[derive(Debug, Clone, Default, Serialize)] @@ -509,19 +525,9 @@ pub struct SurchargeDetails { pub surcharge_amount: MinorUnit, /// tax on surcharge amount for this payment pub tax_on_surcharge_amount: MinorUnit, - /// sum of original amount, - pub final_amount: 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) -> MinorUnit { self.surcharge_amount + self.tax_on_surcharge_amount } @@ -543,14 +549,13 @@ impl 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, + original_amount: payment_attempt.net_amount.get_order_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, } } } @@ -577,7 +582,7 @@ pub struct AuthenticationData { pub eci: Option, pub cavv: String, pub threeds_server_transaction_id: String, - pub message_version: common_types::SemanticVersion, + pub message_version: common_utils::types::SemanticVersion, pub ds_trans_id: Option, } @@ -599,12 +604,13 @@ pub struct RefundsData { pub connector_metadata: Option, pub browser_info: Option, /// Charges associated with the payment - pub charges: Option, + pub split_refunds: Option, // New amount for amount frame work pub minor_payment_amount: MinorUnit, pub minor_refund_amount: MinorUnit, pub integrity_object: Option, + pub refund_status: storage_enums::RefundStatus, } #[derive(Debug, Clone, PartialEq)] @@ -615,6 +621,19 @@ pub struct RefundIntegrityObject { pub refund_amount: MinorUnit, } +#[derive(Debug, serde::Deserialize, Clone)] +pub enum SplitRefundsRequest { + StripeSplitRefund(StripeSplitRefund), +} + +#[derive(Debug, serde::Deserialize, Clone)] +pub struct StripeSplitRefund { + pub charge_id: String, + pub transfer_account_id: String, + pub charge_type: api_models::enums::PaymentChargeType, + pub options: ChargeRefundsOptions, +} + #[derive(Debug, serde::Deserialize, Clone)] pub struct ChargeRefunds { pub charge_id: String, @@ -811,10 +830,11 @@ pub struct PaymentsSessionData { pub currency: common_enums::Currency, pub country: Option, pub surcharge_details: Option, - pub order_details: Option>, - + pub order_details: Option>, + pub email: Option, // Minor Unit amount for amount frame work pub minor_amount: MinorUnit, + pub apple_pay_recurring_details: Option, } #[derive(Debug, Clone, Default)] @@ -822,14 +842,20 @@ pub struct PaymentsTaxCalculationData { pub amount: MinorUnit, pub currency: storage_enums::Currency, pub shipping_cost: Option, - pub order_details: Option>, - pub shipping_address: Address, + pub order_details: Option>, + pub shipping_address: address::Address, } #[derive(Debug, Clone, Default)] pub struct SdkPaymentsSessionUpdateData { pub order_tax_amount: MinorUnit, - pub net_amount: MinorUnit, + // amount here would include amount, surcharge_amount, order_tax_amount and shipping_cost + pub amount: MinorUnit, + /// original amount sent by the merchant + pub order_amount: MinorUnit, + pub currency: storage_enums::Currency, + pub session_id: Option, + pub shipping_cost: Option, } #[derive(Debug, Clone)] @@ -855,4 +881,5 @@ pub struct SetupMandateRequestData { // MinorUnit for amount framework pub minor_amount: Option, + pub shipping_cost: Option, } diff --git a/crates/hyperswitch_domain_models/src/router_request_types/authentication.rs b/crates/hyperswitch_domain_models/src/router_request_types/authentication.rs index ab36deb425f5..067c6042ec20 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types/authentication.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types/authentication.rs @@ -4,7 +4,7 @@ use error_stack::{Report, ResultExt}; use serde::{Deserialize, Serialize}; use crate::{ - errors::api_error_response::ApiErrorResponse, payment_method_data::PaymentMethodData, + address, errors::api_error_response::ApiErrorResponse, payment_method_data::PaymentMethodData, router_request_types::BrowserInformation, }; @@ -77,8 +77,8 @@ pub struct PreAuthNRequestData { #[derive(Clone, Debug)] pub struct ConnectorAuthenticationRequestData { pub payment_method_data: PaymentMethodData, - pub billing_address: api_models::payments::Address, - pub shipping_address: Option, + pub billing_address: address::Address, + pub shipping_address: Option, pub browser_details: Option, pub amount: Option, pub currency: 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 index 30a87c5c13d2..4d7f4bfdf495 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types/fraud_check.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types/fraud_check.rs @@ -4,6 +4,7 @@ use common_utils::{ events::{ApiEventMetric, ApiEventsType}, pii::Email, }; +use diesel_models::types::OrderDetailsWithAmount; use masking::Secret; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; @@ -12,7 +13,7 @@ use crate::router_request_types; #[derive(Debug, Clone)] pub struct FraudCheckSaleData { pub amount: i64, - pub order_details: Option>, + pub order_details: Option>, pub currency: Option, pub email: Option, } @@ -20,7 +21,7 @@ pub struct FraudCheckSaleData { #[derive(Debug, Clone)] pub struct FraudCheckCheckoutData { pub amount: i64, - pub order_details: Option>, + pub order_details: Option>, pub currency: Option, pub browser_info: Option, pub payment_method_data: Option, @@ -31,7 +32,7 @@ pub struct FraudCheckCheckoutData { #[derive(Debug, Clone)] pub struct FraudCheckTransactionData { pub amount: i64, - pub order_details: Option>, + pub order_details: Option>, pub currency: Option, pub payment_method: Option, pub error_code: Option, diff --git a/crates/hyperswitch_domain_models/src/router_request_types/unified_authentication_service.rs b/crates/hyperswitch_domain_models/src/router_request_types/unified_authentication_service.rs new file mode 100644 index 000000000000..af2a940cb51b --- /dev/null +++ b/crates/hyperswitch_domain_models/src/router_request_types/unified_authentication_service.rs @@ -0,0 +1,58 @@ +use masking::Secret; + +#[derive(Clone, serde::Deserialize, Debug, serde::Serialize)] +pub struct UasPreAuthenticationRequestData { + pub service_details: Option, + pub transaction_details: Option, +} + +#[derive(Clone, serde::Deserialize, Debug, serde::Serialize)] +pub struct CtpServiceDetails { + pub service_session_ids: Option, +} + +#[derive(Clone, serde::Deserialize, Debug, serde::Serialize)] +pub struct ServiceSessionIds { + pub correlation_id: Option, + pub merchant_transaction_id: Option, + pub x_src_flow_id: Option, +} + +#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)] +pub struct TransactionDetails { + pub amount: common_utils::types::MinorUnit, + pub currency: common_enums::Currency, +} + +#[derive(Clone, Debug)] +pub struct UasPostAuthenticationRequestData {} + +#[derive(Debug, Clone)] +pub enum UasAuthenticationResponseData { + PreAuthentication {}, + PostAuthentication { + authentication_details: PostAuthenticationDetails, + }, +} + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +pub struct PostAuthenticationDetails { + pub eci: Option, + pub token_details: TokenDetails, + pub dynamic_data_details: Option, +} + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +pub struct TokenDetails { + pub payment_token: cards::CardNumber, + pub payment_account_reference: String, + pub token_expiration_month: Secret, + pub token_expiration_year: Secret, +} + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +pub struct DynamicData { + pub dynamic_data_value: Option>, + pub dynamic_data_type: String, + pub ds_trans_id: Option, +} diff --git a/crates/hyperswitch_domain_models/src/router_response_types.rs b/crates/hyperswitch_domain_models/src/router_response_types.rs index 79a78efb5383..61453f36b84e 100644 --- a/crates/hyperswitch_domain_models/src/router_response_types.rs +++ b/crates/hyperswitch_domain_models/src/router_response_types.rs @@ -5,7 +5,10 @@ use std::collections::HashMap; use common_utils::{request::Method, types as common_types, types::MinorUnit}; pub use disputes::{AcceptDisputeResponse, DefendDisputeResponse, SubmitEvidenceResponse}; -use crate::router_request_types::{authentication::AuthNFlowType, ResponseId}; +use crate::{ + errors::api_error_response::ApiErrorResponse, + router_request_types::{authentication::AuthNFlowType, ResponseId}, +}; #[derive(Debug, Clone)] pub struct RefundsResponseData { pub connector_refund_id: String, @@ -17,8 +20,8 @@ pub struct RefundsResponseData { pub enum PaymentsResponseData { TransactionResponse { resource_id: ResponseId, - redirection_data: Option, - mandate_reference: Option, + redirection_data: Box>, + mandate_reference: Box>, connector_metadata: Option, network_txn_id: Option, connector_response_reference_id: Option, @@ -68,9 +71,9 @@ pub enum PaymentsResponseData { PostProcessingResponse { session_token: Option, }, - // SessionUpdateResponse { - // status: common_enums::SessionUpdateStatus, - // }, + SessionUpdateResponse { + status: common_enums::SessionUpdateStatus, + }, } #[derive(Debug, Clone)] @@ -82,7 +85,8 @@ pub struct TaxCalculationResponseData { pub struct MandateReference { pub connector_mandate_id: Option, pub payment_method_id: Option, - pub mandate_metadata: Option, + pub mandate_metadata: Option, + pub connector_mandate_request_reference_id: Option, } #[derive(Debug, Clone)] @@ -118,6 +122,91 @@ impl CaptureSyncResponse { } } } +impl PaymentsResponseData { + pub fn get_connector_metadata(&self) -> Option> { + match self { + Self::TransactionResponse { + connector_metadata, .. + } + | Self::PreProcessingResponse { + connector_metadata, .. + } => connector_metadata.clone().map(masking::Secret::new), + _ => None, + } + } + + pub fn get_connector_transaction_id( + &self, + ) -> Result> { + match self { + Self::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(txn_id), + .. + } => Ok(txn_id.to_string()), + _ => Err(ApiErrorResponse::MissingRequiredField { + field_name: "ConnectorTransactionId", + } + .into()), + } + } + pub fn merge_transaction_responses( + auth_response: &Self, + capture_response: &Self, + ) -> Result> { + match (auth_response, capture_response) { + ( + Self::TransactionResponse { + resource_id: _, + redirection_data: auth_redirection_data, + mandate_reference: auth_mandate_reference, + connector_metadata: auth_connector_metadata, + network_txn_id: auth_network_txn_id, + connector_response_reference_id: auth_connector_response_reference_id, + incremental_authorization_allowed: auth_incremental_auth_allowed, + charge_id: auth_charge_id, + }, + Self::TransactionResponse { + resource_id: capture_resource_id, + redirection_data: capture_redirection_data, + mandate_reference: capture_mandate_reference, + connector_metadata: capture_connector_metadata, + network_txn_id: capture_network_txn_id, + connector_response_reference_id: capture_connector_response_reference_id, + incremental_authorization_allowed: capture_incremental_auth_allowed, + charge_id: capture_charge_id, + }, + ) => Ok(Self::TransactionResponse { + resource_id: capture_resource_id.clone(), + redirection_data: Box::new( + capture_redirection_data + .clone() + .or_else(|| *auth_redirection_data.clone()), + ), + mandate_reference: Box::new( + auth_mandate_reference + .clone() + .or_else(|| *capture_mandate_reference.clone()), + ), + connector_metadata: capture_connector_metadata + .clone() + .or(auth_connector_metadata.clone()), + network_txn_id: capture_network_txn_id + .clone() + .or(auth_network_txn_id.clone()), + connector_response_reference_id: capture_connector_response_reference_id + .clone() + .or(auth_connector_response_reference_id.clone()), + incremental_authorization_allowed: (*capture_incremental_auth_allowed) + .or(*auth_incremental_auth_allowed), + charge_id: capture_charge_id.clone().or(auth_charge_id.clone()), + }), + _ => Err(ApiErrorResponse::NotSupported { + message: "Invalid Flow ".to_owned(), + } + .into()), + } + } +} #[derive(Debug, Clone)] pub enum PreprocessingResponseId { @@ -163,6 +252,12 @@ pub enum RedirectForm { Mifinity { initialization_token: String, }, + WorldpayDDCForm { + endpoint: url::Url, + method: Method, + form_fields: HashMap, + collection_id: Option, + }, } impl From<(url::Url, Method)> for RedirectForm { @@ -184,6 +279,162 @@ impl From<(url::Url, Method)> for RedirectForm { } } +impl From for diesel_models::payment_attempt::RedirectForm { + fn from(redirect_form: RedirectForm) -> Self { + match redirect_form { + RedirectForm::Form { + endpoint, + method, + form_fields, + } => Self::Form { + endpoint, + method, + form_fields, + }, + RedirectForm::Html { html_data } => Self::Html { html_data }, + RedirectForm::BlueSnap { + payment_fields_token, + } => Self::BlueSnap { + payment_fields_token, + }, + RedirectForm::CybersourceAuthSetup { + access_token, + ddc_url, + reference_id, + } => Self::CybersourceAuthSetup { + access_token, + ddc_url, + reference_id, + }, + RedirectForm::CybersourceConsumerAuth { + access_token, + step_up_url, + } => Self::CybersourceConsumerAuth { + access_token, + step_up_url, + }, + RedirectForm::Payme => Self::Payme, + RedirectForm::Braintree { + client_token, + card_token, + bin, + } => Self::Braintree { + client_token, + card_token, + bin, + }, + RedirectForm::Nmi { + amount, + currency, + public_key, + customer_vault_id, + order_id, + } => Self::Nmi { + amount, + currency, + public_key, + customer_vault_id, + order_id, + }, + RedirectForm::Mifinity { + initialization_token, + } => Self::Mifinity { + initialization_token, + }, + RedirectForm::WorldpayDDCForm { + endpoint, + method, + form_fields, + collection_id, + } => Self::WorldpayDDCForm { + endpoint: common_utils::types::Url::wrap(endpoint), + method, + form_fields, + collection_id, + }, + } + } +} + +impl From for RedirectForm { + fn from(redirect_form: diesel_models::payment_attempt::RedirectForm) -> Self { + match redirect_form { + diesel_models::payment_attempt::RedirectForm::Form { + endpoint, + method, + form_fields, + } => Self::Form { + endpoint, + method, + form_fields, + }, + diesel_models::payment_attempt::RedirectForm::Html { html_data } => { + Self::Html { html_data } + } + diesel_models::payment_attempt::RedirectForm::BlueSnap { + payment_fields_token, + } => Self::BlueSnap { + payment_fields_token, + }, + diesel_models::payment_attempt::RedirectForm::CybersourceAuthSetup { + access_token, + ddc_url, + reference_id, + } => Self::CybersourceAuthSetup { + access_token, + ddc_url, + reference_id, + }, + diesel_models::payment_attempt::RedirectForm::CybersourceConsumerAuth { + access_token, + step_up_url, + } => Self::CybersourceConsumerAuth { + access_token, + step_up_url, + }, + diesel_models::payment_attempt::RedirectForm::Payme => Self::Payme, + diesel_models::payment_attempt::RedirectForm::Braintree { + client_token, + card_token, + bin, + } => Self::Braintree { + client_token, + card_token, + bin, + }, + diesel_models::payment_attempt::RedirectForm::Nmi { + amount, + currency, + public_key, + customer_vault_id, + order_id, + } => Self::Nmi { + amount, + currency, + public_key, + customer_vault_id, + order_id, + }, + diesel_models::payment_attempt::RedirectForm::Mifinity { + initialization_token, + } => Self::Mifinity { + initialization_token, + }, + diesel_models::payment_attempt::RedirectForm::WorldpayDDCForm { + endpoint, + method, + form_fields, + collection_id, + } => Self::WorldpayDDCForm { + endpoint: endpoint.into_inner(), + method, + form_fields, + collection_id, + }, + } + } +} + #[derive(Default, Clone, Debug)] pub struct UploadFileResponse { pub provider_file_id: String, @@ -260,3 +511,56 @@ pub struct CompleteAuthorizeRedirectResponse { pub params: Option>, pub payload: Option, } + +/// Represents details of a payment method. +#[derive(Debug, Clone)] +pub struct PaymentMethodDetails { + /// Indicates whether mandates are supported by this payment method. + pub mandates: common_enums::FeatureStatus, + /// Indicates whether refund is supported by this payment method. + pub refunds: common_enums::FeatureStatus, + /// List of supported capture methods + pub supported_capture_methods: Vec, +} + +/// list of payment method types and metadata related to them +pub type PaymentMethodTypeMetadata = HashMap; + +/// list of payment methods, payment method types and metadata related to them +pub type SupportedPaymentMethods = HashMap; + +#[derive(Debug, Clone)] +pub struct ConnectorInfo { + /// Description of the connector. + pub description: String, + + /// Connector Type + pub connector_type: common_enums::PaymentConnectorCategory, +} + +pub trait SupportedPaymentMethodsExt { + fn add( + &mut self, + payment_method: common_enums::PaymentMethod, + payment_method_type: common_enums::PaymentMethodType, + payment_method_details: PaymentMethodDetails, + ); +} + +impl SupportedPaymentMethodsExt for SupportedPaymentMethods { + fn add( + &mut self, + payment_method: common_enums::PaymentMethod, + payment_method_type: common_enums::PaymentMethodType, + payment_method_details: PaymentMethodDetails, + ) { + if let Some(payment_method_data) = self.get_mut(&payment_method) { + payment_method_data.insert(payment_method_type, payment_method_details); + } else { + let mut payment_method_type_metadata = PaymentMethodTypeMetadata::new(); + payment_method_type_metadata.insert(payment_method_type, payment_method_details); + + self.insert(payment_method, payment_method_type_metadata); + } + } +} diff --git a/crates/hyperswitch_domain_models/src/type_encryption.rs b/crates/hyperswitch_domain_models/src/type_encryption.rs index 983528dee981..07ff1caf7afa 100644 --- a/crates/hyperswitch_domain_models/src/type_encryption.rs +++ b/crates/hyperswitch_domain_models/src/type_encryption.rs @@ -18,6 +18,7 @@ mod encrypt { crypto, encryption::Encryption, errors::{self, CustomResult}, + ext_traits::ByteSliceExt, keymanager::call_encryption_service, transformers::{ForeignFrom, ForeignTryFrom}, types::keymanager::{ @@ -32,7 +33,7 @@ mod encrypt { use router_env::{instrument, logger, tracing}; use rustc_hash::FxHashMap; - use super::metrics; + use super::{metrics, EncryptedJsonType}; #[async_trait] pub trait TypeEncryption< @@ -115,8 +116,8 @@ mod encrypt { S: masking::Strategy + Send + Sync, > TypeEncryption for crypto::Encryptable> { + // Do not remove the `skip_all` as the key would be logged otherwise #[instrument(skip_all)] - #[allow(unused_variables)] async fn encrypt_via_api( state: &KeyManagerState, masked_data: Secret, @@ -142,7 +143,7 @@ mod encrypt { Ok(response) => Ok(ForeignFrom::foreign_from((masked_data.clone(), response))), Err(err) => { logger::error!("Encryption error {:?}", err); - metrics::ENCRYPTION_API_FAILURES.add(&metrics::CONTEXT, 1, &[]); + metrics::ENCRYPTION_API_FAILURES.add(1, &[]); logger::info!("Fall back to Application Encryption"); Self::encrypt(masked_data, key, crypt_algo).await } @@ -150,8 +151,8 @@ mod encrypt { } } + // Do not remove the `skip_all` as the key would be logged otherwise #[instrument(skip_all)] - #[allow(unused_variables)] async fn decrypt_via_api( state: &KeyManagerState, encrypted_data: Encryption, @@ -186,7 +187,7 @@ mod encrypt { match decrypted { Ok(de) => Ok(de), Err(_) => { - metrics::DECRYPTION_API_FAILURES.add(&metrics::CONTEXT, 1, &[]); + metrics::DECRYPTION_API_FAILURES.add(1, &[]); logger::info!("Fall back to Application Decryption"); Self::decrypt(encrypted_data, key, crypt_algo).await } @@ -194,22 +195,26 @@ mod encrypt { } } + // Do not remove the `skip_all` as the key would be logged otherwise + #[instrument(skip_all)] async fn encrypt( masked_data: Secret, key: &[u8], crypt_algo: V, ) -> CustomResult { - metrics::APPLICATION_ENCRYPTION_COUNT.add(&metrics::CONTEXT, 1, &[]); + metrics::APPLICATION_ENCRYPTION_COUNT.add(1, &[]); let encrypted_data = crypt_algo.encode_message(key, masked_data.peek().as_bytes())?; Ok(Self::new(masked_data, encrypted_data.into())) } + // Do not remove the `skip_all` as the key would be logged otherwise + #[instrument(skip_all)] async fn decrypt( encrypted_data: Encryption, key: &[u8], crypt_algo: V, ) -> CustomResult { - metrics::APPLICATION_DECRYPTION_COUNT.add(&metrics::CONTEXT, 1, &[]); + metrics::APPLICATION_DECRYPTION_COUNT.add(1, &[]); let encrypted = encrypted_data.into_inner(); let data = crypt_algo.decode_message(key, encrypted.clone())?; @@ -220,7 +225,8 @@ mod encrypt { Ok(Self::new(value.into(), encrypted)) } - #[allow(unused_variables)] + // Do not remove the `skip_all` as the key would be logged otherwise + #[instrument(skip_all)] async fn batch_encrypt_via_api( state: &KeyManagerState, masked_data: FxHashMap>, @@ -245,7 +251,7 @@ mod encrypt { match result { Ok(response) => Ok(ForeignFrom::foreign_from((masked_data, response))), Err(err) => { - metrics::ENCRYPTION_API_FAILURES.add(&metrics::CONTEXT, 1, &[]); + metrics::ENCRYPTION_API_FAILURES.add(1, &[]); logger::error!("Encryption error {:?}", err); logger::info!("Fall back to Application Encryption"); Self::batch_encrypt(masked_data, key, crypt_algo).await @@ -254,7 +260,8 @@ mod encrypt { } } - #[allow(unused_variables)] + // Do not remove the `skip_all` as the key would be logged otherwise + #[instrument(skip_all)] async fn batch_decrypt_via_api( state: &KeyManagerState, encrypted_data: FxHashMap, @@ -288,7 +295,7 @@ mod encrypt { match decrypted { Ok(de) => Ok(de), Err(_) => { - metrics::DECRYPTION_API_FAILURES.add(&metrics::CONTEXT, 1, &[]); + metrics::DECRYPTION_API_FAILURES.add(1, &[]); logger::info!("Fall back to Application Decryption"); Self::batch_decrypt(encrypted_data, key, crypt_algo).await } @@ -296,12 +303,14 @@ mod encrypt { } } + // Do not remove the `skip_all` as the key would be logged otherwise + #[instrument(skip_all)] async fn batch_encrypt( masked_data: FxHashMap>, key: &[u8], crypt_algo: V, ) -> CustomResult, errors::CryptoError> { - metrics::APPLICATION_ENCRYPTION_COUNT.add(&metrics::CONTEXT, 1, &[]); + metrics::APPLICATION_ENCRYPTION_COUNT.add(1, &[]); masked_data .into_iter() .map(|(k, v)| { @@ -316,12 +325,14 @@ mod encrypt { .collect() } + // Do not remove the `skip_all` as the key would be logged otherwise + #[instrument(skip_all)] async fn batch_decrypt( encrypted_data: FxHashMap, key: &[u8], crypt_algo: V, ) -> CustomResult, errors::CryptoError> { - metrics::APPLICATION_DECRYPTION_COUNT.add(&metrics::CONTEXT, 1, &[]); + metrics::APPLICATION_DECRYPTION_COUNT.add(1, &[]); encrypted_data .into_iter() .map(|(k, v)| { @@ -342,8 +353,8 @@ mod encrypt { > TypeEncryption for crypto::Encryptable> { + // Do not remove the `skip_all` as the key would be logged otherwise #[instrument(skip_all)] - #[allow(unused_variables)] async fn encrypt_via_api( state: &KeyManagerState, masked_data: Secret, @@ -369,7 +380,7 @@ mod encrypt { Ok(response) => Ok(ForeignFrom::foreign_from((masked_data.clone(), response))), Err(err) => { logger::error!("Encryption error {:?}", err); - metrics::ENCRYPTION_API_FAILURES.add(&metrics::CONTEXT, 1, &[]); + metrics::ENCRYPTION_API_FAILURES.add(1, &[]); logger::info!("Fall back to Application Encryption"); Self::encrypt(masked_data, key, crypt_algo).await } @@ -377,8 +388,8 @@ mod encrypt { } } + // Do not remove the `skip_all` as the key would be logged otherwise #[instrument(skip_all)] - #[allow(unused_variables)] async fn decrypt_via_api( state: &KeyManagerState, encrypted_data: Encryption, @@ -412,7 +423,7 @@ mod encrypt { match decrypted { Ok(de) => Ok(de), Err(_) => { - metrics::DECRYPTION_API_FAILURES.add(&metrics::CONTEXT, 1, &[]); + metrics::DECRYPTION_API_FAILURES.add(1, &[]); logger::info!("Fall back to Application Decryption"); Self::decrypt(encrypted_data, key, crypt_algo).await } @@ -420,26 +431,28 @@ mod encrypt { } } + // Do not remove the `skip_all` as the key would be logged otherwise #[instrument(skip_all)] async fn encrypt( masked_data: Secret, key: &[u8], crypt_algo: V, ) -> CustomResult { - metrics::APPLICATION_ENCRYPTION_COUNT.add(&metrics::CONTEXT, 1, &[]); + metrics::APPLICATION_ENCRYPTION_COUNT.add(1, &[]); let data = serde_json::to_vec(&masked_data.peek()) .change_context(errors::CryptoError::DecodingFailed)?; let encrypted_data = crypt_algo.encode_message(key, &data)?; Ok(Self::new(masked_data, encrypted_data.into())) } + // Do not remove the `skip_all` as the key would be logged otherwise #[instrument(skip_all)] async fn decrypt( encrypted_data: Encryption, key: &[u8], crypt_algo: V, ) -> CustomResult { - metrics::APPLICATION_DECRYPTION_COUNT.add(&metrics::CONTEXT, 1, &[]); + metrics::APPLICATION_DECRYPTION_COUNT.add(1, &[]); let encrypted = encrypted_data.into_inner(); let data = crypt_algo.decode_message(key, encrypted.clone())?; @@ -448,7 +461,8 @@ mod encrypt { Ok(Self::new(value.into(), encrypted)) } - #[allow(unused_variables)] + // Do not remove the `skip_all` as the key would be logged otherwise + #[instrument(skip_all)] async fn batch_encrypt_via_api( state: &KeyManagerState, masked_data: FxHashMap>, @@ -473,7 +487,7 @@ mod encrypt { match result { Ok(response) => Ok(ForeignFrom::foreign_from((masked_data, response))), Err(err) => { - metrics::ENCRYPTION_API_FAILURES.add(&metrics::CONTEXT, 1, &[]); + metrics::ENCRYPTION_API_FAILURES.add(1, &[]); logger::error!("Encryption error {:?}", err); logger::info!("Fall back to Application Encryption"); Self::batch_encrypt(masked_data, key, crypt_algo).await @@ -482,7 +496,8 @@ mod encrypt { } } - #[allow(unused_variables)] + // Do not remove the `skip_all` as the key would be logged otherwise + #[instrument(skip_all)] async fn batch_decrypt_via_api( state: &KeyManagerState, encrypted_data: FxHashMap, @@ -516,7 +531,7 @@ mod encrypt { match decrypted { Ok(de) => Ok(de), Err(_) => { - metrics::DECRYPTION_API_FAILURES.add(&metrics::CONTEXT, 1, &[]); + metrics::DECRYPTION_API_FAILURES.add(1, &[]); logger::info!("Fall back to Application Decryption"); Self::batch_decrypt(encrypted_data, key, crypt_algo).await } @@ -524,12 +539,14 @@ mod encrypt { } } + // Do not remove the `skip_all` as the key would be logged otherwise + #[instrument(skip_all)] async fn batch_encrypt( masked_data: FxHashMap>, key: &[u8], crypt_algo: V, ) -> CustomResult, errors::CryptoError> { - metrics::APPLICATION_ENCRYPTION_COUNT.add(&metrics::CONTEXT, 1, &[]); + metrics::APPLICATION_ENCRYPTION_COUNT.add(1, &[]); masked_data .into_iter() .map(|(k, v)| { @@ -543,12 +560,14 @@ mod encrypt { .collect() } + // Do not remove the `skip_all` as the key would be logged otherwise + #[instrument(skip_all)] async fn batch_decrypt( encrypted_data: FxHashMap, key: &[u8], crypt_algo: V, ) -> CustomResult, errors::CryptoError> { - metrics::APPLICATION_DECRYPTION_COUNT.add(&metrics::CONTEXT, 1, &[]); + metrics::APPLICATION_DECRYPTION_COUNT.add(1, &[]); encrypted_data .into_iter() .map(|(k, v)| { @@ -562,14 +581,251 @@ mod encrypt { } } + impl EncryptedJsonType + where + T: std::fmt::Debug + Clone + serde::Serialize + serde::de::DeserializeOwned, + { + fn serialize_json_bytes(&self) -> CustomResult>, errors::CryptoError> { + common_utils::ext_traits::Encode::encode_to_vec(self.inner()) + .change_context(errors::CryptoError::EncodingFailed) + .attach_printable("Failed to JSON serialize data before encryption") + .map(Secret::new) + } + + fn deserialize_json_bytes( + bytes: Secret>, + ) -> CustomResult, errors::ParsingError> + where + S: masking::Strategy, + { + bytes + .peek() + .as_slice() + .parse_struct::(std::any::type_name::()) + .map(|result| Secret::new(Self::from(result))) + .attach_printable("Failed to JSON deserialize data after decryption") + } + } + + #[async_trait] + impl< + T: std::fmt::Debug + Clone + serde::Serialize + serde::de::DeserializeOwned + Send, + V: crypto::DecodeMessage + crypto::EncodeMessage + Send + 'static, + S: masking::Strategy> + Send + Sync, + > TypeEncryption, V, S> + for crypto::Encryptable, S>> + { + // Do not remove the `skip_all` as the key would be logged otherwise + #[instrument(skip_all)] + async fn encrypt_via_api( + state: &KeyManagerState, + masked_data: Secret, S>, + identifier: Identifier, + key: &[u8], + crypt_algo: V, + ) -> CustomResult { + let data_bytes = EncryptedJsonType::serialize_json_bytes(masked_data.peek())?; + let result: crypto::Encryptable>> = + TypeEncryption::encrypt_via_api(state, data_bytes, identifier, key, crypt_algo) + .await?; + Ok(Self::new(masked_data, result.into_encrypted())) + } + + // Do not remove the `skip_all` as the key would be logged otherwise + #[instrument(skip_all)] + async fn decrypt_via_api( + state: &KeyManagerState, + encrypted_data: Encryption, + identifier: Identifier, + key: &[u8], + crypt_algo: V, + ) -> CustomResult { + let result: crypto::Encryptable>> = + TypeEncryption::decrypt_via_api(state, encrypted_data, identifier, key, crypt_algo) + .await?; + result + .deserialize_inner_value(EncryptedJsonType::deserialize_json_bytes) + .change_context(errors::CryptoError::DecodingFailed) + } + + // Do not remove the `skip_all` as the key would be logged otherwise + #[instrument(skip_all)] + async fn encrypt( + masked_data: Secret, S>, + key: &[u8], + crypt_algo: V, + ) -> CustomResult { + let data_bytes = EncryptedJsonType::serialize_json_bytes(masked_data.peek())?; + let result: crypto::Encryptable>> = + TypeEncryption::encrypt(data_bytes, key, crypt_algo).await?; + Ok(Self::new(masked_data, result.into_encrypted())) + } + + // Do not remove the `skip_all` as the key would be logged otherwise + #[instrument(skip_all)] + async fn decrypt( + encrypted_data: Encryption, + key: &[u8], + crypt_algo: V, + ) -> CustomResult { + let result: crypto::Encryptable>> = + TypeEncryption::decrypt(encrypted_data, key, crypt_algo).await?; + result + .deserialize_inner_value(EncryptedJsonType::deserialize_json_bytes) + .change_context(errors::CryptoError::DecodingFailed) + } + + // Do not remove the `skip_all` as the key would be logged otherwise + #[instrument(skip_all)] + async fn batch_encrypt_via_api( + state: &KeyManagerState, + masked_data: FxHashMap, S>>, + identifier: Identifier, + key: &[u8], + crypt_algo: V, + ) -> CustomResult, errors::CryptoError> { + let hashmap_capacity = masked_data.len(); + let data_bytes = masked_data.iter().try_fold( + FxHashMap::with_capacity_and_hasher(hashmap_capacity, Default::default()), + |mut map, (key, value)| { + let value_bytes = EncryptedJsonType::serialize_json_bytes(value.peek())?; + map.insert(key.to_owned(), value_bytes); + Ok::<_, error_stack::Report>(map) + }, + )?; + + let result: FxHashMap>>> = + TypeEncryption::batch_encrypt_via_api( + state, data_bytes, identifier, key, crypt_algo, + ) + .await?; + let result_hashmap = result.into_iter().try_fold( + FxHashMap::with_capacity_and_hasher(hashmap_capacity, Default::default()), + |mut map, (key, value)| { + let original_value = masked_data + .get(&key) + .ok_or(errors::CryptoError::EncodingFailed) + .attach_printable_lazy(|| { + format!("Failed to find {key} in input hashmap") + })?; + map.insert( + key, + Self::new(original_value.clone(), value.into_encrypted()), + ); + Ok::<_, error_stack::Report>(map) + }, + )?; + + Ok(result_hashmap) + } + + // Do not remove the `skip_all` as the key would be logged otherwise + #[instrument(skip_all)] + async fn batch_decrypt_via_api( + state: &KeyManagerState, + encrypted_data: FxHashMap, + identifier: Identifier, + key: &[u8], + crypt_algo: V, + ) -> CustomResult, errors::CryptoError> { + let result: FxHashMap>>> = + TypeEncryption::batch_decrypt_via_api( + state, + encrypted_data, + identifier, + key, + crypt_algo, + ) + .await?; + + let hashmap_capacity = result.len(); + let result_hashmap = result.into_iter().try_fold( + FxHashMap::with_capacity_and_hasher(hashmap_capacity, Default::default()), + |mut map, (key, value)| { + let deserialized_value = value + .deserialize_inner_value(EncryptedJsonType::deserialize_json_bytes) + .change_context(errors::CryptoError::DecodingFailed)?; + map.insert(key, deserialized_value); + Ok::<_, error_stack::Report>(map) + }, + )?; + + Ok(result_hashmap) + } + + // Do not remove the `skip_all` as the key would be logged otherwise + #[instrument(skip_all)] + async fn batch_encrypt( + masked_data: FxHashMap, S>>, + key: &[u8], + crypt_algo: V, + ) -> CustomResult, errors::CryptoError> { + let hashmap_capacity = masked_data.len(); + let data_bytes = masked_data.iter().try_fold( + FxHashMap::with_capacity_and_hasher(hashmap_capacity, Default::default()), + |mut map, (key, value)| { + let value_bytes = EncryptedJsonType::serialize_json_bytes(value.peek())?; + map.insert(key.to_owned(), value_bytes); + Ok::<_, error_stack::Report>(map) + }, + )?; + + let result: FxHashMap>>> = + TypeEncryption::batch_encrypt(data_bytes, key, crypt_algo).await?; + let result_hashmap = result.into_iter().try_fold( + FxHashMap::with_capacity_and_hasher(hashmap_capacity, Default::default()), + |mut map, (key, value)| { + let original_value = masked_data + .get(&key) + .ok_or(errors::CryptoError::EncodingFailed) + .attach_printable_lazy(|| { + format!("Failed to find {key} in input hashmap") + })?; + map.insert( + key, + Self::new(original_value.clone(), value.into_encrypted()), + ); + Ok::<_, error_stack::Report>(map) + }, + )?; + + Ok(result_hashmap) + } + + // Do not remove the `skip_all` as the key would be logged otherwise + #[instrument(skip_all)] + async fn batch_decrypt( + encrypted_data: FxHashMap, + key: &[u8], + crypt_algo: V, + ) -> CustomResult, errors::CryptoError> { + let result: FxHashMap>>> = + TypeEncryption::batch_decrypt(encrypted_data, key, crypt_algo).await?; + + let hashmap_capacity = result.len(); + let result_hashmap = result.into_iter().try_fold( + FxHashMap::with_capacity_and_hasher(hashmap_capacity, Default::default()), + |mut map, (key, value)| { + let deserialized_value = value + .deserialize_inner_value(EncryptedJsonType::deserialize_json_bytes) + .change_context(errors::CryptoError::DecodingFailed)?; + map.insert(key, deserialized_value); + Ok::<_, error_stack::Report>(map) + }, + )?; + + Ok(result_hashmap) + } + } + #[async_trait] impl< V: crypto::DecodeMessage + crypto::EncodeMessage + Send + 'static, S: masking::Strategy> + Send + Sync, > TypeEncryption, V, S> for crypto::Encryptable, S>> { + // Do not remove the `skip_all` as the key would be logged otherwise #[instrument(skip_all)] - #[allow(unused_variables)] async fn encrypt_via_api( state: &KeyManagerState, masked_data: Secret, S>, @@ -595,7 +851,7 @@ mod encrypt { Ok(response) => Ok(ForeignFrom::foreign_from((masked_data.clone(), response))), Err(err) => { logger::error!("Encryption error {:?}", err); - metrics::ENCRYPTION_API_FAILURES.add(&metrics::CONTEXT, 1, &[]); + metrics::ENCRYPTION_API_FAILURES.add(1, &[]); logger::info!("Fall back to Application Encryption"); Self::encrypt(masked_data, key, crypt_algo).await } @@ -603,8 +859,8 @@ mod encrypt { } } + // Do not remove the `skip_all` as the key would be logged otherwise #[instrument(skip_all)] - #[allow(unused_variables)] async fn decrypt_via_api( state: &KeyManagerState, encrypted_data: Encryption, @@ -638,7 +894,7 @@ mod encrypt { match decrypted { Ok(de) => Ok(de), Err(_) => { - metrics::DECRYPTION_API_FAILURES.add(&metrics::CONTEXT, 1, &[]); + metrics::DECRYPTION_API_FAILURES.add(1, &[]); logger::info!("Fall back to Application Decryption"); Self::decrypt(encrypted_data, key, crypt_algo).await } @@ -646,30 +902,33 @@ mod encrypt { } } + // Do not remove the `skip_all` as the key would be logged otherwise #[instrument(skip_all)] async fn encrypt( masked_data: Secret, S>, key: &[u8], crypt_algo: V, ) -> CustomResult { - metrics::APPLICATION_ENCRYPTION_COUNT.add(&metrics::CONTEXT, 1, &[]); + metrics::APPLICATION_ENCRYPTION_COUNT.add(1, &[]); let encrypted_data = crypt_algo.encode_message(key, masked_data.peek())?; Ok(Self::new(masked_data, encrypted_data.into())) } + // Do not remove the `skip_all` as the key would be logged otherwise #[instrument(skip_all)] async fn decrypt( encrypted_data: Encryption, key: &[u8], crypt_algo: V, ) -> CustomResult { - metrics::APPLICATION_DECRYPTION_COUNT.add(&metrics::CONTEXT, 1, &[]); + metrics::APPLICATION_DECRYPTION_COUNT.add(1, &[]); let encrypted = encrypted_data.into_inner(); let data = crypt_algo.decode_message(key, encrypted.clone())?; Ok(Self::new(data.into(), encrypted)) } - #[allow(unused_variables)] + // Do not remove the `skip_all` as the key would be logged otherwise + #[instrument(skip_all)] async fn batch_encrypt_via_api( state: &KeyManagerState, masked_data: FxHashMap, S>>, @@ -694,7 +953,7 @@ mod encrypt { match result { Ok(response) => Ok(ForeignFrom::foreign_from((masked_data, response))), Err(err) => { - metrics::ENCRYPTION_API_FAILURES.add(&metrics::CONTEXT, 1, &[]); + metrics::ENCRYPTION_API_FAILURES.add(1, &[]); logger::error!("Encryption error {:?}", err); logger::info!("Fall back to Application Encryption"); Self::batch_encrypt(masked_data, key, crypt_algo).await @@ -703,7 +962,8 @@ mod encrypt { } } - #[allow(unused_variables)] + // Do not remove the `skip_all` as the key would be logged otherwise + #[instrument(skip_all)] async fn batch_decrypt_via_api( state: &KeyManagerState, encrypted_data: FxHashMap, @@ -737,7 +997,7 @@ mod encrypt { match decrypted { Ok(de) => Ok(de), Err(_) => { - metrics::DECRYPTION_API_FAILURES.add(&metrics::CONTEXT, 1, &[]); + metrics::DECRYPTION_API_FAILURES.add(1, &[]); logger::info!("Fall back to Application Decryption"); Self::batch_decrypt(encrypted_data, key, crypt_algo).await } @@ -745,12 +1005,14 @@ mod encrypt { } } + // Do not remove the `skip_all` as the key would be logged otherwise + #[instrument(skip_all)] async fn batch_encrypt( masked_data: FxHashMap, S>>, key: &[u8], crypt_algo: V, ) -> CustomResult, errors::CryptoError> { - metrics::APPLICATION_ENCRYPTION_COUNT.add(&metrics::CONTEXT, 1, &[]); + metrics::APPLICATION_ENCRYPTION_COUNT.add(1, &[]); masked_data .into_iter() .map(|(k, v)| { @@ -762,12 +1024,14 @@ mod encrypt { .collect() } + // Do not remove the `skip_all` as the key would be logged otherwise + #[instrument(skip_all)] async fn batch_decrypt( encrypted_data: FxHashMap, key: &[u8], crypt_algo: V, ) -> CustomResult, errors::CryptoError> { - metrics::APPLICATION_DECRYPTION_COUNT.add(&metrics::CONTEXT, 1, &[]); + metrics::APPLICATION_DECRYPTION_COUNT.add(1, &[]); encrypted_data .into_iter() .map(|(k, v)| { @@ -785,6 +1049,37 @@ mod encrypt { } } } + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct EncryptedJsonType(T); + +impl EncryptedJsonType { + pub fn inner(&self) -> &T { + &self.0 + } + + pub fn into_inner(self) -> T { + self.0 + } +} + +impl From for EncryptedJsonType { + fn from(value: T) -> Self { + Self(value) + } +} + +impl std::ops::Deref for EncryptedJsonType { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.inner() + } +} + +/// Type alias for `Option>>>` +pub type OptionalEncryptableJsonType = Option>>>; + pub trait Lift { type SelfWrapper; type OtherWrapper; @@ -845,7 +1140,6 @@ where record_operation_time( crypto::Encryptable::encrypt_via_api(state, inner, identifier, key, crypto::GcmAes256), &metrics::ENCRYPTION_TIME, - &metrics::CONTEXT, &[], ) .await @@ -872,7 +1166,6 @@ where crypto::GcmAes256, ), &metrics::ENCRYPTION_TIME, - &metrics::CONTEXT, &[], ) .await @@ -928,7 +1221,6 @@ where record_operation_time( crypto::Encryptable::decrypt_via_api(state, inner, identifier, key, crypto::GcmAes256), &metrics::DECRYPTION_TIME, - &metrics::CONTEXT, &[], ) .await @@ -955,7 +1247,6 @@ where crypto::GcmAes256, ), &metrics::ENCRYPTION_TIME, - &metrics::CONTEXT, &[], ) .await @@ -983,6 +1274,7 @@ pub enum CryptoOutput> { BatchOperation(FxHashMap>>), } +// Do not remove the `skip_all` as the key would be logged otherwise #[instrument(skip_all, fields(table = table_name))] pub async fn crypto_operation>( state: &KeyManagerState, @@ -1024,14 +1316,13 @@ where } pub(crate) mod metrics { - use router_env::{counter_metric, global_meter, histogram_metric, metrics_context, once_cell}; + use router_env::{counter_metric, global_meter, histogram_metric_f64, once_cell}; - metrics_context!(CONTEXT); global_meter!(GLOBAL_METER, "ROUTER_API"); // Encryption and Decryption metrics - histogram_metric!(ENCRYPTION_TIME, GLOBAL_METER); - histogram_metric!(DECRYPTION_TIME, GLOBAL_METER); + histogram_metric_f64!(ENCRYPTION_TIME, GLOBAL_METER); + histogram_metric_f64!(DECRYPTION_TIME, GLOBAL_METER); counter_metric!(ENCRYPTION_API_FAILURES, GLOBAL_METER); counter_metric!(DECRYPTION_API_FAILURES, GLOBAL_METER); counter_metric!(APPLICATION_ENCRYPTION_COUNT, GLOBAL_METER); diff --git a/crates/hyperswitch_domain_models/src/types.rs b/crates/hyperswitch_domain_models/src/types.rs index a6be862403d0..2dd38d88b6d9 100644 --- a/crates/hyperswitch_domain_models/src/types.rs +++ b/crates/hyperswitch_domain_models/src/types.rs @@ -1,15 +1,23 @@ +pub use diesel_models::types::OrderDetailsWithAmount; + use crate::{ router_data::{AccessToken, RouterData}, router_flow_types::{ AccessTokenAuth, Authorize, AuthorizeSessionToken, CalculateTax, Capture, - CompleteAuthorize, CreateConnectorCustomer, PSync, PaymentMethodToken, RSync, SetupMandate, - Void, + CompleteAuthorize, CreateConnectorCustomer, Execute, PSync, PaymentMethodToken, + PostAuthenticate, PostSessionTokens, PreAuthenticate, PreProcessing, RSync, Session, + SetupMandate, Void, }, router_request_types::{ + unified_authentication_service::{ + UasAuthenticationResponseData, UasPostAuthenticationRequestData, + UasPreAuthenticationRequestData, + }, AccessTokenRequestData, AuthorizeSessionTokenData, CompleteAuthorizeData, ConnectorCustomerData, PaymentMethodTokenizationData, PaymentsAuthorizeData, - PaymentsCancelData, PaymentsCaptureData, PaymentsSyncData, PaymentsTaxCalculationData, - RefundsData, SetupMandateRequestData, + PaymentsCancelData, PaymentsCaptureData, PaymentsPostSessionTokensData, + PaymentsPreProcessingData, PaymentsSessionData, PaymentsSyncData, + PaymentsTaxCalculationData, RefundsData, SetupMandateRequestData, }, router_response_types::{ PaymentsResponseData, RefundsResponseData, TaxCalculationResponseData, @@ -20,12 +28,15 @@ pub type PaymentsAuthorizeRouterData = RouterData; pub type PaymentsAuthorizeSessionTokenRouterData = RouterData; +pub type PaymentsPreProcessingRouterData = + RouterData; pub type PaymentsSyncRouterData = RouterData; pub type PaymentsCaptureRouterData = RouterData; pub type PaymentsCancelRouterData = RouterData; pub type SetupMandateRouterData = RouterData; pub type RefundsRouterData = RouterData; +pub type RefundExecuteRouterData = RouterData; pub type RefundSyncRouterData = RouterData; pub type TokenizationRouterData = RouterData; @@ -36,3 +47,12 @@ pub type PaymentsCompleteAuthorizeRouterData = pub type PaymentsTaxCalculationRouterData = RouterData; pub type RefreshTokenRouterData = RouterData; +pub type PaymentsPostSessionTokensRouterData = + RouterData; +pub type PaymentsSessionRouterData = RouterData; + +pub type UasPostAuthenticationRouterData = + RouterData; + +pub type UasPreAuthenticationRouterData = + RouterData; diff --git a/crates/hyperswitch_interfaces/Cargo.toml b/crates/hyperswitch_interfaces/Cargo.toml index 7ab885de3a36..99a06ff2e3a8 100644 --- a/crates/hyperswitch_interfaces/Cargo.toml +++ b/crates/hyperswitch_interfaces/Cargo.toml @@ -9,7 +9,7 @@ license.workspace = true [features] default = ["dummy_connector", "frm", "payouts"] dummy_connector = [] -v1 = ["hyperswitch_domain_models/v1", "api_models/v1"] +v1 = ["hyperswitch_domain_models/v1", "api_models/v1", "common_utils/v1"] payouts = ["hyperswitch_domain_models/payouts"] frm = ["hyperswitch_domain_models/frm"] diff --git a/crates/hyperswitch_interfaces/src/api.rs b/crates/hyperswitch_interfaces/src/api.rs index 7205865f24ac..ca2d806762ad 100644 --- a/crates/hyperswitch_interfaces/src/api.rs +++ b/crates/hyperswitch_interfaces/src/api.rs @@ -16,7 +16,11 @@ pub mod payouts; pub mod payouts_v2; pub mod refunds; pub mod refunds_v2; -use common_enums::enums::{CallConnectorAction, CaptureMethod, PaymentAction, PaymentMethodType}; + +use common_enums::{ + enums::{CallConnectorAction, CaptureMethod, EventClass, PaymentAction, PaymentMethodType}, + PaymentMethod, +}; use common_utils::{ errors::CustomResult, request::{Method, Request, RequestContent}, @@ -27,17 +31,29 @@ use hyperswitch_domain_models::{ router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, router_data_v2::{ flow_common_types::WebhookSourceVerifyData, AccessTokenFlowData, MandateRevokeFlowData, + UasFlowData, + }, + router_flow_types::{ + mandate_revoke::MandateRevoke, AccessTokenAuth, PostAuthenticate, PreAuthenticate, + VerifyWebhookSource, }, - router_flow_types::{mandate_revoke::MandateRevoke, AccessTokenAuth, VerifyWebhookSource}, router_request_types::{ + unified_authentication_service::{ + UasAuthenticationResponseData, UasPostAuthenticationRequestData, + UasPreAuthenticationRequestData, + }, AccessTokenRequestData, MandateRevokeRequestData, VerifyWebhookSourceRequestData, }, - router_response_types::{MandateRevokeResponseData, VerifyWebhookSourceResponseData}, + router_response_types::{ + ConnectorInfo, MandateRevokeResponseData, PaymentMethodDetails, SupportedPaymentMethods, + VerifyWebhookSourceResponseData, + }, }; use masking::Maskable; -use router_env::metrics::add_attributes; use serde_json::json; +#[cfg(feature = "payouts")] +pub use self::payouts::*; pub use self::{payments::*, refunds::*}; use crate::{ configs::Connectors, connector_integration_v2::ConnectorIntegrationV2, consts, errors, @@ -119,9 +135,8 @@ pub trait ConnectorIntegration: _connectors: &Connectors, ) -> CustomResult, errors::ConnectorError> { metrics::UNIMPLEMENTED_FLOW.add( - &metrics::CONTEXT, 1, - &add_attributes([("connector", req.connector.clone())]), + router_env::metric_attributes!(("connector", req.connector.clone())), ); Ok(None) } @@ -272,6 +287,24 @@ pub trait ConnectorCommon { } } +/// The trait that provides specifications about the connector +pub trait ConnectorSpecifications { + /// Details related to payment method supported by the connector + fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> { + None + } + + /// Supported webhooks flows + fn get_supported_webhook_flows(&self) -> Option<&'static [EventClass]> { + None + } + + /// About the connector + fn get_connector_about(&self) -> Option<&'static ConnectorInfo> { + None + } +} + /// Extended trait for connector common to allow functions with generic type pub trait ConnectorCommonExt: ConnectorCommon + ConnectorIntegration @@ -336,24 +369,101 @@ pub trait ConnectorVerifyWebhookSourceV2: { } +/// trait UnifiedAuthenticationService +pub trait UnifiedAuthenticationService: + ConnectorCommon + UasPreAuthentication + UasPostAuthentication +{ +} + +/// trait UasPreAuthentication +pub trait UasPreAuthentication: + ConnectorIntegration< + PreAuthenticate, + UasPreAuthenticationRequestData, + UasAuthenticationResponseData, +> +{ +} + +/// trait UasPostAuthentication +pub trait UasPostAuthentication: + ConnectorIntegration< + PostAuthenticate, + UasPostAuthenticationRequestData, + UasAuthenticationResponseData, +> +{ +} + +/// trait UnifiedAuthenticationServiceV2 +pub trait UnifiedAuthenticationServiceV2: + ConnectorCommon + UasPreAuthenticationV2 + UasPostAuthenticationV2 +{ +} + +///trait UasPreAuthenticationV2 +pub trait UasPreAuthenticationV2: + ConnectorIntegrationV2< + PreAuthenticate, + UasFlowData, + UasPreAuthenticationRequestData, + UasAuthenticationResponseData, +> +{ +} + +/// trait UasPostAuthenticationV2 +pub trait UasPostAuthenticationV2: + ConnectorIntegrationV2< + PostAuthenticate, + UasFlowData, + UasPostAuthenticationRequestData, + UasAuthenticationResponseData, +> +{ +} + /// trait ConnectorValidation -pub trait ConnectorValidation: ConnectorCommon { - /// fn validate_capture_method - fn validate_capture_method( +pub trait ConnectorValidation: ConnectorCommon + ConnectorSpecifications { + /// Validate, the payment request against the connector supported features + fn validate_connector_against_payment_request( &self, capture_method: Option, - _pmt: Option, + payment_method: PaymentMethod, + pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); - match capture_method { - CaptureMethod::Automatic => Ok(()), - CaptureMethod::Manual | CaptureMethod::ManualMultiple | CaptureMethod::Scheduled => { - Err(errors::ConnectorError::NotSupported { - message: capture_method.to_string(), - connector: self.id(), - } - .into()) + let is_default_capture_method = + [CaptureMethod::Automatic, CaptureMethod::SequentialAutomatic] + .contains(&capture_method); + let is_feature_supported = match self.get_supported_payment_methods() { + Some(supported_payment_methods) => { + let connector_payment_method_type_info = get_connector_payment_method_type_info( + supported_payment_methods, + payment_method, + pmt, + self.id(), + )?; + + connector_payment_method_type_info + .map(|payment_method_type_info| { + payment_method_type_info + .supported_capture_methods + .contains(&capture_method) + }) + .unwrap_or_else(|| is_default_capture_method) + } + None => is_default_capture_method, + }; + + if is_feature_supported { + Ok(()) + } else { + Err(errors::ConnectorError::NotSupported { + message: capture_method.to_string(), + connector: self.id(), } + .into()) } } @@ -410,3 +520,34 @@ pub trait ConnectorRedirectResponse { Ok(CallConnectorAction::Avoid) } } + +/// Empty trait for when payouts feature is disabled +#[cfg(not(feature = "payouts"))] +pub trait Payouts {} + +fn get_connector_payment_method_type_info( + supported_payment_method: &SupportedPaymentMethods, + payment_method: PaymentMethod, + payment_method_type: Option, + connector: &'static str, +) -> CustomResult, errors::ConnectorError> { + let payment_method_details = + supported_payment_method + .get(&payment_method) + .ok_or_else(|| errors::ConnectorError::NotSupported { + message: payment_method.to_string(), + connector, + })?; + + payment_method_type + .map(|pmt| { + payment_method_details.get(&pmt).cloned().ok_or_else(|| { + errors::ConnectorError::NotSupported { + message: format!("{} {}", payment_method, pmt), + connector, + } + .into() + }) + }) + .transpose() +} diff --git a/crates/hyperswitch_interfaces/src/api/payments.rs b/crates/hyperswitch_interfaces/src/api/payments.rs index 43409680dc55..ab327d0629a6 100644 --- a/crates/hyperswitch_interfaces/src/api/payments.rs +++ b/crates/hyperswitch_interfaces/src/api/payments.rs @@ -4,14 +4,15 @@ use hyperswitch_domain_models::{ router_flow_types::payments::{ Approve, Authorize, AuthorizeSessionToken, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, IncrementalAuthorization, PSync, PaymentMethodToken, - PostProcessing, PreProcessing, Reject, SdkSessionUpdate, Session, SetupMandate, Void, + PostProcessing, PostSessionTokens, PreProcessing, Reject, SdkSessionUpdate, Session, + SetupMandate, Void, }, router_request_types::{ AuthorizeSessionTokenData, CompleteAuthorizeData, ConnectorCustomerData, PaymentMethodTokenizationData, PaymentsApproveData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsIncrementalAuthorizationData, - PaymentsPostProcessingData, PaymentsPreProcessingData, PaymentsRejectData, - PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, + PaymentsPostProcessingData, PaymentsPostSessionTokensData, PaymentsPreProcessingData, + PaymentsRejectData, PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, SdkPaymentsSessionUpdateData, SetupMandateRequestData, }, router_response_types::{PaymentsResponseData, TaxCalculationResponseData}, @@ -22,6 +23,7 @@ use crate::api; /// trait Payment pub trait Payment: api::ConnectorCommon + + api::ConnectorSpecifications + api::ConnectorValidation + PaymentAuthorize + PaymentAuthorizeSessionToken @@ -39,6 +41,7 @@ pub trait Payment: + ConnectorCustomer + PaymentIncrementalAuthorization + PaymentSessionUpdate + + PaymentPostSessionTokens { } @@ -124,6 +127,12 @@ pub trait PaymentSessionUpdate: { } +/// trait PostSessionTokens +pub trait PaymentPostSessionTokens: + api::ConnectorIntegration +{ +} + /// trait PaymentsCompleteAuthorize pub trait PaymentsCompleteAuthorize: api::ConnectorIntegration diff --git a/crates/hyperswitch_interfaces/src/api/payments_v2.rs b/crates/hyperswitch_interfaces/src/api/payments_v2.rs index cc5fd01f9f87..366024f7da6e 100644 --- a/crates/hyperswitch_interfaces/src/api/payments_v2.rs +++ b/crates/hyperswitch_interfaces/src/api/payments_v2.rs @@ -5,20 +5,23 @@ use hyperswitch_domain_models::{ router_flow_types::payments::{ Approve, Authorize, AuthorizeSessionToken, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, IncrementalAuthorization, PSync, PaymentMethodToken, - PostProcessing, PreProcessing, Reject, SdkSessionUpdate, Session, SetupMandate, Void, + PostProcessing, PostSessionTokens, PreProcessing, Reject, SdkSessionUpdate, Session, + SetupMandate, Void, }, router_request_types::{ AuthorizeSessionTokenData, CompleteAuthorizeData, ConnectorCustomerData, PaymentMethodTokenizationData, PaymentsApproveData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsIncrementalAuthorizationData, - PaymentsPostProcessingData, PaymentsPreProcessingData, PaymentsRejectData, - PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, + PaymentsPostProcessingData, PaymentsPostSessionTokensData, PaymentsPreProcessingData, + PaymentsRejectData, PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, SdkPaymentsSessionUpdateData, SetupMandateRequestData, }, router_response_types::{PaymentsResponseData, TaxCalculationResponseData}, }; -use crate::api::{ConnectorCommon, ConnectorIntegrationV2, ConnectorValidation}; +use crate::api::{ + ConnectorCommon, ConnectorIntegrationV2, ConnectorSpecifications, ConnectorValidation, +}; /// trait PaymentAuthorizeV2 pub trait PaymentAuthorizeV2: @@ -112,6 +115,17 @@ pub trait PaymentSessionUpdateV2: { } +///trait PaymentPostSessionTokensV2 +pub trait PaymentPostSessionTokensV2: + ConnectorIntegrationV2< + PostSessionTokens, + PaymentFlowData, + PaymentsPostSessionTokensData, + PaymentsResponseData, +> +{ +} + /// trait PaymentsCompleteAuthorizeV2 pub trait PaymentsCompleteAuthorizeV2: ConnectorIntegrationV2< @@ -170,6 +184,7 @@ pub trait PaymentsPostProcessingV2: /// trait PaymentV2 pub trait PaymentV2: ConnectorCommon + + ConnectorSpecifications + ConnectorValidation + PaymentAuthorizeV2 + PaymentAuthorizeSessionTokenV2 @@ -188,5 +203,6 @@ pub trait PaymentV2: + PaymentIncrementalAuthorizationV2 + TaxCalculationV2 + PaymentSessionUpdateV2 + + PaymentPostSessionTokensV2 { } diff --git a/crates/hyperswitch_interfaces/src/api/payouts.rs b/crates/hyperswitch_interfaces/src/api/payouts.rs index 894f636707a4..5fc0280f19c0 100644 --- a/crates/hyperswitch_interfaces/src/api/payouts.rs +++ b/crates/hyperswitch_interfaces/src/api/payouts.rs @@ -1,13 +1,15 @@ //! Payouts interface -use hyperswitch_domain_models::router_flow_types::payouts::{ - PoCancel, PoCreate, PoEligibility, PoFulfill, PoQuote, PoRecipient, PoRecipientAccount, PoSync, -}; -#[cfg(feature = "payouts")] use hyperswitch_domain_models::{ - router_request_types::PayoutsData, router_response_types::PayoutsResponseData, + router_flow_types::payouts::{ + PoCancel, PoCreate, PoEligibility, PoFulfill, PoQuote, PoRecipient, PoRecipientAccount, + PoSync, + }, + router_request_types::PayoutsData, + router_response_types::PayoutsResponseData, }; +use super::ConnectorCommon; use crate::api::ConnectorIntegration; /// trait PayoutCancel @@ -42,3 +44,17 @@ pub trait PayoutRecipientAccount: /// trait PayoutSync pub trait PayoutSync: ConnectorIntegration {} + +/// trait Payouts +pub trait Payouts: + ConnectorCommon + + PayoutCancel + + PayoutCreate + + PayoutEligibility + + PayoutFulfill + + PayoutQuote + + PayoutRecipient + + PayoutRecipientAccount + + PayoutSync +{ +} diff --git a/crates/hyperswitch_interfaces/src/api/payouts_v2.rs b/crates/hyperswitch_interfaces/src/api/payouts_v2.rs index 40e0726ce80b..9027152f02d5 100644 --- a/crates/hyperswitch_interfaces/src/api/payouts_v2.rs +++ b/crates/hyperswitch_interfaces/src/api/payouts_v2.rs @@ -1,13 +1,15 @@ //! Payouts V2 interface -use hyperswitch_domain_models::router_flow_types::payouts::{ - PoCancel, PoCreate, PoEligibility, PoFulfill, PoQuote, PoRecipient, PoRecipientAccount, PoSync, -}; -#[cfg(feature = "payouts")] use hyperswitch_domain_models::{ - router_data_v2::flow_common_types::PayoutFlowData, router_request_types::PayoutsData, + router_data_v2::flow_common_types::PayoutFlowData, + router_flow_types::payouts::{ + PoCancel, PoCreate, PoEligibility, PoFulfill, PoQuote, PoRecipient, PoRecipientAccount, + PoSync, + }, + router_request_types::PayoutsData, router_response_types::PayoutsResponseData, }; +use super::ConnectorCommon; use crate::api::ConnectorIntegrationV2; /// trait PayoutCancelV2 @@ -57,3 +59,21 @@ pub trait PayoutSyncV2: ConnectorIntegrationV2 { } + +/// trait Payouts +pub trait PayoutsV2: + ConnectorCommon + + PayoutCancelV2 + + PayoutCreateV2 + + PayoutEligibilityV2 + + PayoutFulfillV2 + + PayoutQuoteV2 + + PayoutRecipientV2 + + PayoutRecipientAccountV2 + + PayoutSyncV2 +{ +} + +/// Empty trait for when payouts feature is disabled +#[cfg(not(feature = "payouts"))] +pub trait PayoutsV2 {} diff --git a/crates/hyperswitch_interfaces/src/configs.rs b/crates/hyperswitch_interfaces/src/configs.rs index 793328f98a45..4a3b99636dc3 100644 --- a/crates/hyperswitch_interfaces/src/configs.rs +++ b/crates/hyperswitch_interfaces/src/configs.rs @@ -3,7 +3,6 @@ use common_enums::ApplicationError; use masking::Secret; use router_derive; use serde::Deserialize; - // struct Connectors #[allow(missing_docs, missing_debug_implementations)] #[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)] @@ -13,6 +12,7 @@ pub struct Connectors { pub adyen: AdyenParamsWithThreeBaseUrls, pub adyenplatform: ConnectorParams, pub airwallex: ConnectorParams, + pub amazonpay: ConnectorParams, pub applepay: ConnectorParams, pub authorizedotnet: ConnectorParams, pub bambora: ConnectorParams, @@ -27,13 +27,16 @@ pub struct Connectors { pub checkout: ConnectorParams, pub coinbase: ConnectorParams, pub cryptopay: ConnectorParams, + pub ctp_mastercard: NoParams, pub cybersource: ConnectorParams, pub datatrans: ConnectorParams, pub deutschebank: ConnectorParams, + pub digitalvirgo: ConnectorParams, pub dlocal: ConnectorParams, #[cfg(feature = "dummy_connector")] pub dummyconnector: ConnectorParams, pub ebanx: ConnectorParams, + pub elavon: ConnectorParams, pub fiserv: ConnectorParams, pub fiservemea: ConnectorParams, pub fiuu: ConnectorParamsWithThreeUrls, @@ -44,7 +47,9 @@ pub struct Connectors { pub gpayments: ConnectorParams, pub helcim: ConnectorParams, pub iatapay: ConnectorParams, + pub inespay: ConnectorParams, pub itaubank: ConnectorParams, + pub jpmorgan: ConnectorParams, pub klarna: ConnectorParams, pub mifinity: ConnectorParams, pub mollie: ConnectorParams, @@ -53,6 +58,7 @@ pub struct Connectors { pub nexinets: ConnectorParams, pub nexixpay: ConnectorParams, pub nmi: ConnectorParams, + pub nomupay: ConnectorParams, pub noon: ConnectorParamsWithModeType, pub novalnet: ConnectorParams, pub nuvei: ConnectorParams, @@ -70,6 +76,7 @@ pub struct Connectors { pub prophetpay: ConnectorParams, pub rapyd: ConnectorParams, pub razorpay: ConnectorParamsWithKeys, + pub redsys: ConnectorParams, pub riskified: ConnectorParams, pub shift4: ConnectorParams, pub signifyd: ConnectorParams, @@ -81,12 +88,14 @@ pub struct Connectors { pub thunes: ConnectorParams, pub trustpay: ConnectorParamsWithMoreUrls, pub tsys: ConnectorParams, + pub unified_authentication_service: ConnectorParams, pub volt: ConnectorParams, pub wellsfargo: ConnectorParams, pub wellsfargopayout: ConnectorParams, pub wise: ConnectorParams, pub worldline: ConnectorParams, pub worldpay: ConnectorParams, + pub xendit: ConnectorParams, pub zen: ConnectorParams, pub zsl: ConnectorParams, } @@ -101,6 +110,17 @@ pub struct ConnectorParams { pub secondary_base_url: Option, } +///struct No Param for connectors with no params +#[derive(Debug, Deserialize, Clone, Default)] +pub struct NoParams; + +impl NoParams { + /// function to satisfy connector param validation macro + pub fn validate(&self, _parent_field: &str) -> Result<(), ApplicationError> { + Ok(()) + } +} + /// struct ConnectorParamsWithKeys #[derive(Debug, Deserialize, Clone, Default, router_derive::ConfigValidate)] #[serde(default)] diff --git a/crates/hyperswitch_interfaces/src/connector_integration_v2.rs b/crates/hyperswitch_interfaces/src/connector_integration_v2.rs index 9384ed0a0cda..bc18f5c66394 100644 --- a/crates/hyperswitch_interfaces/src/connector_integration_v2.rs +++ b/crates/hyperswitch_interfaces/src/connector_integration_v2.rs @@ -5,7 +5,6 @@ use common_utils::{ }; use hyperswitch_domain_models::{router_data::ErrorResponse, router_data_v2::RouterDataV2}; use masking::Maskable; -use router_env::metrics::add_attributes; use serde_json::json; use crate::{ @@ -65,11 +64,8 @@ pub trait ConnectorIntegrationV2: &self, _req: &RouterDataV2, ) -> CustomResult { - metrics::UNIMPLEMENTED_FLOW.add( - &metrics::CONTEXT, - 1, - &add_attributes([("connector", self.id())]), - ); + metrics::UNIMPLEMENTED_FLOW + .add(1, router_env::metric_attributes!(("connector", self.id()))); Ok(String::new()) } diff --git a/crates/hyperswitch_interfaces/src/disputes.rs b/crates/hyperswitch_interfaces/src/disputes.rs index acc7b56e906e..55a6d13c91d1 100644 --- a/crates/hyperswitch_interfaces/src/disputes.rs +++ b/crates/hyperswitch_interfaces/src/disputes.rs @@ -8,7 +8,7 @@ pub struct DisputePayload { /// amount pub amount: String, /// currency - pub currency: String, + pub currency: common_enums::enums::Currency, /// dispute_stage pub dispute_stage: common_enums::enums::DisputeStage, /// connector_status diff --git a/crates/hyperswitch_interfaces/src/events/connector_api_logs.rs b/crates/hyperswitch_interfaces/src/events/connector_api_logs.rs index 5edd8d68c32d..bf9eafe5aaff 100644 --- a/crates/hyperswitch_interfaces/src/events/connector_api_logs.rs +++ b/crates/hyperswitch_interfaces/src/events/connector_api_logs.rs @@ -9,6 +9,7 @@ use time::OffsetDateTime; /// struct ConnectorEvent #[derive(Debug, Serialize)] pub struct ConnectorEvent { + tenant_id: common_utils::id_type::TenantId, connector_name: String, flow: String, request: String, @@ -31,6 +32,7 @@ impl ConnectorEvent { /// fn new ConnectorEvent #[allow(clippy::too_many_arguments)] pub fn new( + tenant_id: common_utils::id_type::TenantId, connector_name: String, flow: &str, request: serde_json::Value, @@ -45,6 +47,7 @@ impl ConnectorEvent { status_code: u16, ) -> Self { Self { + tenant_id, connector_name, flow: flow .rsplit_once("::") diff --git a/crates/hyperswitch_interfaces/src/metrics.rs b/crates/hyperswitch_interfaces/src/metrics.rs index fc374eba8e24..84aa6d10be09 100644 --- a/crates/hyperswitch_interfaces/src/metrics.rs +++ b/crates/hyperswitch_interfaces/src/metrics.rs @@ -1,8 +1,7 @@ //! Metrics interface -use router_env::{counter_metric, global_meter, metrics_context}; +use router_env::{counter_metric, global_meter}; -metrics_context!(CONTEXT); global_meter!(GLOBAL_METER, "ROUTER_API"); counter_metric!(UNIMPLEMENTED_FLOW, GLOBAL_METER); diff --git a/crates/hyperswitch_interfaces/src/secrets_interface.rs b/crates/hyperswitch_interfaces/src/secrets_interface.rs index 761981bedda1..6944729191a7 100644 --- a/crates/hyperswitch_interfaces/src/secrets_interface.rs +++ b/crates/hyperswitch_interfaces/src/secrets_interface.rs @@ -10,11 +10,13 @@ use masking::Secret; /// Trait defining the interface for managing application secrets #[async_trait::async_trait] pub trait SecretManagementInterface: Send + Sync { + /* /// Given an input, encrypt/store the secret - // async fn store_secret( - // &self, - // input: Secret, - // ) -> CustomResult; + async fn store_secret( + &self, + input: Secret, + ) -> CustomResult; + */ /// Given an input, decrypt/retrieve the secret async fn get_secret( diff --git a/crates/hyperswitch_interfaces/src/secrets_interface/secret_state.rs b/crates/hyperswitch_interfaces/src/secrets_interface/secret_state.rs index d1da6a8c8b68..9573dfa12cb8 100644 --- a/crates/hyperswitch_interfaces/src/secrets_interface/secret_state.rs +++ b/crates/hyperswitch_interfaces/src/secrets_interface/secret_state.rs @@ -26,17 +26,13 @@ pub struct SecretStateContainer { } impl SecretStateContainer { - /// /// Get the inner data while consuming self - /// #[inline] pub fn into_inner(self) -> T { self.inner } - /// /// Get the reference to inner value - /// #[inline] pub fn get_inner(&self) -> &T { &self.inner diff --git a/crates/hyperswitch_interfaces/src/types.rs b/crates/hyperswitch_interfaces/src/types.rs index e42942a94de9..eb31fd221d64 100644 --- a/crates/hyperswitch_interfaces/src/types.rs +++ b/crates/hyperswitch_interfaces/src/types.rs @@ -1,4 +1,5 @@ //! Types interface + use hyperswitch_domain_models::{ router_data::AccessToken, router_flow_types::{ @@ -9,20 +10,26 @@ use hyperswitch_domain_models::{ payments::{ Authorize, AuthorizeSessionToken, Balance, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, IncrementalAuthorization, InitPayment, PSync, - PaymentMethodToken, PostProcessing, PreProcessing, Session, SetupMandate, Void, + PaymentMethodToken, PostProcessing, PostSessionTokens, PreProcessing, SdkSessionUpdate, + Session, SetupMandate, Void, }, refunds::{Execute, RSync}, + unified_authentication_service::{PostAuthenticate, PreAuthenticate}, webhooks::VerifyWebhookSource, }, router_request_types::{ + unified_authentication_service::{ + UasAuthenticationResponseData, UasPostAuthenticationRequestData, + UasPreAuthenticationRequestData, + }, AcceptDisputeRequestData, AccessTokenRequestData, AuthorizeSessionTokenData, CompleteAuthorizeData, ConnectorCustomerData, DefendDisputeRequestData, MandateRevokeRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsIncrementalAuthorizationData, - PaymentsPostProcessingData, PaymentsPreProcessingData, PaymentsSessionData, - PaymentsSyncData, PaymentsTaxCalculationData, RefundsData, RetrieveFileRequestData, - SetupMandateRequestData, SubmitEvidenceRequestData, UploadFileRequestData, - VerifyWebhookSourceRequestData, + PaymentsPostProcessingData, PaymentsPostSessionTokensData, PaymentsPreProcessingData, + PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, RefundsData, + RetrieveFileRequestData, SdkPaymentsSessionUpdateData, SetupMandateRequestData, + SubmitEvidenceRequestData, UploadFileRequestData, VerifyWebhookSourceRequestData, }, router_response_types::{ AcceptDisputeResponse, DefendDisputeResponse, MandateRevokeResponseData, @@ -58,6 +65,15 @@ pub type PaymentsAuthorizeType = /// Type alias for `ConnectorIntegration` pub type PaymentsTaxCalculationType = dyn ConnectorIntegration; +/// Type alias for `ConnectorIntegration` +pub type PaymentsPostSessionTokensType = dyn ConnectorIntegration< + PostSessionTokens, + PaymentsPostSessionTokensData, + PaymentsResponseData, +>; +/// Type alias for `ConnectorIntegration` +pub type SdkSessionUpdateType = + dyn ConnectorIntegration; /// Type alias for `ConnectorIntegration` pub type SetupMandateType = dyn ConnectorIntegration; @@ -175,3 +191,17 @@ pub type RetrieveFileType = /// Type alias for `ConnectorIntegration` pub type DefendDisputeType = dyn ConnectorIntegration; + +/// Type alias for `ConnectorIntegration` +pub type UasPreAuthenticationType = dyn ConnectorIntegration< + PreAuthenticate, + UasPreAuthenticationRequestData, + UasAuthenticationResponseData, +>; + +/// Type alias for `ConnectorIntegration` +pub type UasPostAuthenticationType = dyn ConnectorIntegration< + PostAuthenticate, + UasPostAuthenticationRequestData, + UasAuthenticationResponseData, +>; diff --git a/crates/hyperswitch_interfaces/src/webhooks.rs b/crates/hyperswitch_interfaces/src/webhooks.rs index e863af40a391..cc3bd4876450 100644 --- a/crates/hyperswitch_interfaces/src/webhooks.rs +++ b/crates/hyperswitch_interfaces/src/webhooks.rs @@ -2,7 +2,9 @@ use common_utils::{crypto, errors::CustomResult, ext_traits::ValueExt}; use error_stack::ResultExt; -use hyperswitch_domain_models::api::ApplicationResponse; +use hyperswitch_domain_models::{ + api::ApplicationResponse, errors::api_error_response::ApiErrorResponse, +}; use masking::{ExposeInterface, Secret}; use crate::{api::ConnectorCommon, errors}; @@ -22,6 +24,30 @@ pub struct IncomingWebhookRequestDetails<'a> { pub query_params: String, } +/// IncomingWebhookFlowError enum defining the error type for incoming webhook +#[derive(Debug)] +pub enum IncomingWebhookFlowError { + /// Resource not found for the webhook + ResourceNotFound, + /// Internal error for the webhook + InternalError, +} + +impl From<&ApiErrorResponse> for IncomingWebhookFlowError { + fn from(api_error_response: &ApiErrorResponse) -> Self { + match api_error_response { + ApiErrorResponse::WebhookResourceNotFound + | ApiErrorResponse::DisputeNotFound { .. } + | ApiErrorResponse::PayoutNotFound + | ApiErrorResponse::MandateNotFound + | ApiErrorResponse::PaymentNotFound + | ApiErrorResponse::RefundNotFound + | ApiErrorResponse::AuthenticationNotFound { .. } => Self::ResourceNotFound, + _ => Self::InternalError, + } + } +} + /// Trait defining incoming webhook #[async_trait::async_trait] pub trait IncomingWebhook: ConnectorCommon + Sync { @@ -203,6 +229,7 @@ pub trait IncomingWebhook: ConnectorCommon + Sync { fn get_webhook_api_response( &self, _request: &IncomingWebhookRequestDetails<'_>, + _error_kind: Option, ) -> CustomResult, errors::ConnectorError> { Ok(ApplicationResponse::StatusOk) } @@ -226,4 +253,26 @@ pub trait IncomingWebhook: ConnectorCommon + Sync { ) .into()) } + + /// fn get_mandate_details + fn get_mandate_details( + &self, + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult< + Option, + errors::ConnectorError, + > { + Ok(None) + } + + /// fn get_network_txn_id + fn get_network_txn_id( + &self, + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult< + Option, + errors::ConnectorError, + > { + Ok(None) + } } diff --git a/crates/kgraph_utils/Cargo.toml b/crates/kgraph_utils/Cargo.toml index 0b7b580e647d..0883e1486915 100644 --- a/crates/kgraph_utils/Cargo.toml +++ b/crates/kgraph_utils/Cargo.toml @@ -8,8 +8,8 @@ license.workspace = true [features] dummy_connector = ["api_models/dummy_connector", "euclid/dummy_connector"] -v1 = ["api_models/v1"] -v2 = ["api_models/v2"] +v1 = ["api_models/v1", "common_utils/v1"] +v2 = ["api_models/v2", "common_utils/v2"] [dependencies] api_models = { version = "0.1.0", path = "../api_models", package = "api_models" } diff --git a/crates/kgraph_utils/benches/evaluation.rs b/crates/kgraph_utils/benches/evaluation.rs index 858c013f41dd..e492ec46eb5e 100644 --- a/crates/kgraph_utils/benches/evaluation.rs +++ b/crates/kgraph_utils/benches/evaluation.rs @@ -16,6 +16,7 @@ use euclid::{ use hyperswitch_constraint_graph::{CycleCheck, Memoization}; use kgraph_utils::{error::KgraphError, transformers::IntoDirValue, types::CountryCurrencyFilter}; +#[cfg(feature = "v1")] fn build_test_data( total_enabled: usize, total_pm_types: usize, @@ -54,25 +55,25 @@ fn build_test_data( let profile_id = common_utils::generate_profile_id_of_default_length(); - #[cfg(feature = "v2")] - let stripe_account = MerchantConnectorResponse { - connector_type: api_enums::ConnectorType::FizOperations, - connector_name: "stripe".to_string(), - id: common_utils::generate_merchant_connector_account_id_of_default_length(), - connector_account_details: masking::Secret::new(serde_json::json!({})), - disabled: None, - metadata: None, - payment_methods_enabled: Some(pms_enabled), - connector_label: Some("something".to_string()), - frm_configs: None, - connector_webhook_details: None, - profile_id, - applepay_verified_domains: None, - pm_auth_config: None, - status: api_enums::ConnectorStatus::Inactive, - additional_merchant_data: None, - connector_wallets_details: None, - }; + // #[cfg(feature = "v2")] + // let stripe_account = MerchantConnectorResponse { + // connector_type: api_enums::ConnectorType::FizOperations, + // connector_name: "stripe".to_string(), + // id: common_utils::generate_merchant_connector_account_id_of_default_length(), + // connector_account_details: masking::Secret::new(serde_json::json!({})), + // disabled: None, + // metadata: None, + // payment_methods_enabled: Some(pms_enabled), + // connector_label: Some("something".to_string()), + // frm_configs: None, + // connector_webhook_details: None, + // profile_id, + // applepay_verified_domains: None, + // pm_auth_config: None, + // status: api_enums::ConnectorStatus::Inactive, + // additional_merchant_data: None, + // connector_wallets_details: None, + // }; #[cfg(feature = "v1")] let stripe_account = MerchantConnectorResponse { @@ -102,10 +103,13 @@ fn build_test_data( connector_configs: HashMap::new(), default_configs: None, }; + + #[cfg(feature = "v1")] kgraph_utils::mca::make_mca_graph(vec![stripe_account], &config) .expect("Failed graph construction") } +#[cfg(feature = "v1")] fn evaluation(c: &mut Criterion) { let small_graph = build_test_data(3, 8); let big_graph = build_test_data(20, 20); @@ -149,5 +153,10 @@ fn evaluation(c: &mut Criterion) { }); } +#[cfg(feature = "v1")] criterion_group!(benches, evaluation); +#[cfg(feature = "v1")] criterion_main!(benches); + +#[cfg(feature = "v2")] +fn main() {} diff --git a/crates/kgraph_utils/src/mca.rs b/crates/kgraph_utils/src/mca.rs index 2ec0cbe8031b..a3e1dfc6220f 100644 --- a/crates/kgraph_utils/src/mca.rs +++ b/crates/kgraph_utils/src/mca.rs @@ -16,6 +16,7 @@ use crate::{error::KgraphError, transformers::IntoDirValue, types as kgraph_type pub const DOMAIN_IDENTIFIER: &str = "payment_methods_enabled_for_merchantconnectoraccount"; +#[cfg(feature = "v1")] fn get_dir_value_payment_method( from: api_enums::PaymentMethodType, ) -> Result { @@ -146,9 +147,14 @@ fn get_dir_value_payment_method( api_enums::PaymentMethodType::OpenBankingPIS => { Ok(dirval!(OpenBankingType = OpenBankingPIS)) } + api_enums::PaymentMethodType::Paze => Ok(dirval!(WalletType = Paze)), + api_enums::PaymentMethodType::DirectCarrierBilling => { + Ok(dirval!(MobilePaymentType = DirectCarrierBilling)) + } } } +#[cfg(feature = "v1")] fn compile_request_pm_types( builder: &mut cgraph::ConstraintGraphBuilder, pm_types: RequestPaymentMethodTypes, @@ -334,6 +340,7 @@ fn compile_request_pm_types( .map_err(KgraphError::GraphConstructionError) } +#[cfg(feature = "v1")] fn compile_payment_method_enabled( builder: &mut cgraph::ConstraintGraphBuilder, enabled: admin_api::PaymentMethodsEnabled, @@ -401,6 +408,8 @@ macro_rules! collect_global_variants { .collect::>() }; } + +#[cfg(feature = "v1")] fn global_vec_pmt( enabled_pmt: Vec, builder: &mut cgraph::ConstraintGraphBuilder, @@ -420,6 +429,7 @@ fn global_vec_pmt( global_vector.append(collect_global_variants!(BankTransferType)); global_vector.append(collect_global_variants!(CardRedirectType)); global_vector.append(collect_global_variants!(OpenBankingType)); + global_vector.append(collect_global_variants!(MobilePaymentType)); global_vector.push(dir::DirValue::PaymentMethod( dir::enums::PaymentMethod::Card, )); @@ -507,16 +517,17 @@ fn compile_graph_for_countries_and_currencies( .map_err(KgraphError::GraphConstructionError) } +#[cfg(feature = "v1")] fn compile_config_graph( builder: &mut cgraph::ConstraintGraphBuilder, config: &kgraph_types::CountryCurrencyFilter, - connector: &api_enums::RoutableConnectors, + 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) + .get(&connector) .or(config.default_configs.as_ref()) .map(|inner| inner.0.clone()) { @@ -600,6 +611,7 @@ fn compile_config_graph( .map_err(KgraphError::GraphConstructionError) } +#[cfg(feature = "v1")] fn compile_merchant_connector_graph( builder: &mut cgraph::ConstraintGraphBuilder, mca: admin_api::MerchantConnectorResponse, @@ -630,7 +642,7 @@ fn compile_merchant_connector_graph( let config_info = "Config for respective PaymentMethodType for the connector"; - let config_enabled_agg_id = compile_config_graph(builder, config, &connector)?; + let config_enabled_agg_id = compile_config_graph(builder, config, connector)?; let domain_level_node_id = builder .make_all_aggregator( @@ -670,6 +682,7 @@ fn compile_merchant_connector_graph( Ok(()) } +#[cfg(feature = "v1")] pub fn make_mca_graph( accts: Vec, config: &kgraph_types::CountryCurrencyFilter, @@ -686,6 +699,7 @@ pub fn make_mca_graph( Ok(builder.build()) } +#[cfg(feature = "v1")] #[cfg(test)] mod tests { #![allow(clippy::expect_used)] @@ -706,61 +720,61 @@ mod tests { use api_models::{admin::*, payment_methods::*}; let profile_id = common_utils::generate_profile_id_of_default_length(); - #[cfg(feature = "v2")] - let stripe_account = MerchantConnectorResponse { - connector_type: api_enums::ConnectorType::FizOperations, - connector_name: "stripe".to_string(), - id: common_utils::generate_merchant_connector_account_id_of_default_length(), - connector_label: Some("something".to_string()), - connector_account_details: masking::Secret::new(serde_json::json!({})), - disabled: None, - metadata: None, - payment_methods_enabled: Some(vec![PaymentMethodsEnabled { - payment_method: api_enums::PaymentMethod::Card, - payment_method_types: Some(vec![ - RequestPaymentMethodTypes { - payment_method_type: api_enums::PaymentMethodType::Credit, - payment_experience: None, - card_networks: Some(vec![ - api_enums::CardNetwork::Visa, - api_enums::CardNetwork::Mastercard, - ]), - accepted_currencies: Some(AcceptedCurrencies::EnableOnly(vec![ - api_enums::Currency::INR, - ])), - accepted_countries: None, - minimum_amount: Some(MinorUnit::new(10)), - maximum_amount: Some(MinorUnit::new(1000)), - recurring_enabled: true, - installment_payment_enabled: true, - }, - RequestPaymentMethodTypes { - payment_method_type: api_enums::PaymentMethodType::Debit, - payment_experience: None, - card_networks: Some(vec![ - api_enums::CardNetwork::Maestro, - api_enums::CardNetwork::JCB, - ]), - accepted_currencies: Some(AcceptedCurrencies::EnableOnly(vec![ - api_enums::Currency::GBP, - ])), - accepted_countries: None, - minimum_amount: Some(MinorUnit::new(10)), - maximum_amount: Some(MinorUnit::new(1000)), - recurring_enabled: true, - installment_payment_enabled: true, - }, - ]), - }]), - frm_configs: None, - connector_webhook_details: None, - profile_id, - applepay_verified_domains: None, - pm_auth_config: None, - status: api_enums::ConnectorStatus::Inactive, - additional_merchant_data: None, - connector_wallets_details: None, - }; + // #[cfg(feature = "v2")] + // let stripe_account = MerchantConnectorResponse { + // connector_type: api_enums::ConnectorType::FizOperations, + // connector_name: "stripe".to_string(), + // id: common_utils::generate_merchant_connector_account_id_of_default_length(), + // connector_label: Some("something".to_string()), + // connector_account_details: masking::Secret::new(serde_json::json!({})), + // disabled: None, + // metadata: None, + // payment_methods_enabled: Some(vec![PaymentMethodsEnabled { + // payment_method: api_enums::PaymentMethod::Card, + // payment_method_types: Some(vec![ + // RequestPaymentMethodTypes { + // payment_method_type: api_enums::PaymentMethodType::Credit, + // payment_experience: None, + // card_networks: Some(vec![ + // api_enums::CardNetwork::Visa, + // api_enums::CardNetwork::Mastercard, + // ]), + // accepted_currencies: Some(AcceptedCurrencies::EnableOnly(vec![ + // api_enums::Currency::INR, + // ])), + // accepted_countries: None, + // minimum_amount: Some(MinorUnit::new(10)), + // maximum_amount: Some(MinorUnit::new(1000)), + // recurring_enabled: true, + // installment_payment_enabled: true, + // }, + // RequestPaymentMethodTypes { + // payment_method_type: api_enums::PaymentMethodType::Debit, + // payment_experience: None, + // card_networks: Some(vec![ + // api_enums::CardNetwork::Maestro, + // api_enums::CardNetwork::JCB, + // ]), + // accepted_currencies: Some(AcceptedCurrencies::EnableOnly(vec![ + // api_enums::Currency::GBP, + // ])), + // accepted_countries: None, + // minimum_amount: Some(MinorUnit::new(10)), + // maximum_amount: Some(MinorUnit::new(1000)), + // recurring_enabled: true, + // installment_payment_enabled: true, + // }, + // ]), + // }]), + // frm_configs: None, + // connector_webhook_details: None, + // profile_id, + // applepay_verified_domains: None, + // pm_auth_config: None, + // status: api_enums::ConnectorStatus::Inactive, + // additional_merchant_data: None, + // connector_wallets_details: None, + // }; #[cfg(feature = "v1")] let stripe_account = MerchantConnectorResponse { connector_type: api_enums::ConnectorType::FizOperations, diff --git a/crates/kgraph_utils/src/transformers.rs b/crates/kgraph_utils/src/transformers.rs index 758a0dd3de03..89b8c5a34ad0 100644 --- a/crates/kgraph_utils/src/transformers.rs +++ b/crates/kgraph_utils/src/transformers.rs @@ -104,6 +104,7 @@ impl IntoDirValue for api_enums::PaymentMethod { Self::GiftCard => Ok(dirval!(PaymentMethod = GiftCard)), Self::CardRedirect => Ok(dirval!(PaymentMethod = CardRedirect)), Self::OpenBanking => Ok(dirval!(PaymentMethod = OpenBanking)), + Self::MobilePayment => Ok(dirval!(PaymentMethod = MobilePayment)), } } } @@ -158,6 +159,7 @@ impl IntoDirValue for (api_enums::PaymentMethodType, api_enums::PaymentMethod) { | api_enums::PaymentMethod::Reward | api_enums::PaymentMethod::RealTimePayment | api_enums::PaymentMethod::Upi + | api_enums::PaymentMethod::MobilePayment | api_enums::PaymentMethod::Voucher | api_enums::PaymentMethod::OpenBanking | api_enums::PaymentMethod::GiftCard => Err(KgraphError::ContextConstructionError( @@ -176,6 +178,7 @@ impl IntoDirValue for (api_enums::PaymentMethodType, api_enums::PaymentMethod) { | api_enums::PaymentMethod::Reward | api_enums::PaymentMethod::RealTimePayment | api_enums::PaymentMethod::Upi + | api_enums::PaymentMethod::MobilePayment | api_enums::PaymentMethod::Voucher | api_enums::PaymentMethod::OpenBanking | api_enums::PaymentMethod::GiftCard => Err(KgraphError::ContextConstructionError( @@ -195,6 +198,7 @@ impl IntoDirValue for (api_enums::PaymentMethodType, api_enums::PaymentMethod) { | api_enums::PaymentMethod::Reward | api_enums::PaymentMethod::RealTimePayment | api_enums::PaymentMethod::Upi + | api_enums::PaymentMethod::MobilePayment | api_enums::PaymentMethod::Voucher | api_enums::PaymentMethod::OpenBanking | api_enums::PaymentMethod::GiftCard => Err(KgraphError::ContextConstructionError( @@ -307,6 +311,10 @@ impl IntoDirValue for (api_enums::PaymentMethodType, api_enums::PaymentMethod) { api_enums::PaymentMethodType::OpenBankingPIS => { Ok(dirval!(OpenBankingType = OpenBankingPIS)) } + api_enums::PaymentMethodType::Paze => Ok(dirval!(WalletType = Paze)), + api_enums::PaymentMethodType::DirectCarrierBilling => { + Ok(dirval!(MobilePaymentType = DirectCarrierBilling)) + } } } } @@ -333,6 +341,7 @@ impl IntoDirValue for api_enums::Currency { fn into_dir_value(self) -> Result { match self { Self::AED => Ok(dirval!(PaymentCurrency = AED)), + Self::AFN => Ok(dirval!(PaymentCurrency = AFN)), Self::ALL => Ok(dirval!(PaymentCurrency = ALL)), Self::AMD => Ok(dirval!(PaymentCurrency = AMD)), Self::ANG => Ok(dirval!(PaymentCurrency = ANG)), @@ -352,10 +361,12 @@ impl IntoDirValue for api_enums::Currency { Self::BOB => Ok(dirval!(PaymentCurrency = BOB)), Self::BRL => Ok(dirval!(PaymentCurrency = BRL)), Self::BSD => Ok(dirval!(PaymentCurrency = BSD)), + Self::BTN => Ok(dirval!(PaymentCurrency = BTN)), Self::BWP => Ok(dirval!(PaymentCurrency = BWP)), Self::BYN => Ok(dirval!(PaymentCurrency = BYN)), Self::BZD => Ok(dirval!(PaymentCurrency = BZD)), Self::CAD => Ok(dirval!(PaymentCurrency = CAD)), + Self::CDF => Ok(dirval!(PaymentCurrency = CDF)), Self::CHF => Ok(dirval!(PaymentCurrency = CHF)), Self::CLP => Ok(dirval!(PaymentCurrency = CLP)), Self::CNY => Ok(dirval!(PaymentCurrency = CNY)), @@ -369,6 +380,7 @@ impl IntoDirValue for api_enums::Currency { Self::DOP => Ok(dirval!(PaymentCurrency = DOP)), Self::DZD => Ok(dirval!(PaymentCurrency = DZD)), Self::EGP => Ok(dirval!(PaymentCurrency = EGP)), + Self::ERN => Ok(dirval!(PaymentCurrency = ERN)), Self::ETB => Ok(dirval!(PaymentCurrency = ETB)), Self::EUR => Ok(dirval!(PaymentCurrency = EUR)), Self::FJD => Ok(dirval!(PaymentCurrency = FJD)), @@ -390,6 +402,8 @@ impl IntoDirValue for api_enums::Currency { Self::ILS => Ok(dirval!(PaymentCurrency = ILS)), Self::INR => Ok(dirval!(PaymentCurrency = INR)), Self::IQD => Ok(dirval!(PaymentCurrency = IQD)), + Self::IRR => Ok(dirval!(PaymentCurrency = IRR)), + Self::ISK => Ok(dirval!(PaymentCurrency = ISK)), Self::JMD => Ok(dirval!(PaymentCurrency = JMD)), Self::JOD => Ok(dirval!(PaymentCurrency = JOD)), Self::JPY => Ok(dirval!(PaymentCurrency = JPY)), @@ -397,6 +411,7 @@ impl IntoDirValue for api_enums::Currency { Self::KGS => Ok(dirval!(PaymentCurrency = KGS)), Self::KHR => Ok(dirval!(PaymentCurrency = KHR)), Self::KMF => Ok(dirval!(PaymentCurrency = KMF)), + Self::KPW => Ok(dirval!(PaymentCurrency = KPW)), Self::KRW => Ok(dirval!(PaymentCurrency = KRW)), Self::KWD => Ok(dirval!(PaymentCurrency = KWD)), Self::KYD => Ok(dirval!(PaymentCurrency = KYD)), @@ -443,6 +458,7 @@ impl IntoDirValue for api_enums::Currency { Self::SAR => Ok(dirval!(PaymentCurrency = SAR)), Self::SBD => Ok(dirval!(PaymentCurrency = SBD)), Self::SCR => Ok(dirval!(PaymentCurrency = SCR)), + Self::SDG => Ok(dirval!(PaymentCurrency = SDG)), Self::SEK => Ok(dirval!(PaymentCurrency = SEK)), Self::SGD => Ok(dirval!(PaymentCurrency = SGD)), Self::SHP => Ok(dirval!(PaymentCurrency = SHP)), @@ -453,8 +469,11 @@ impl IntoDirValue for api_enums::Currency { Self::SSP => Ok(dirval!(PaymentCurrency = SSP)), Self::STN => Ok(dirval!(PaymentCurrency = STN)), Self::SVC => Ok(dirval!(PaymentCurrency = SVC)), + Self::SYP => Ok(dirval!(PaymentCurrency = SYP)), Self::SZL => Ok(dirval!(PaymentCurrency = SZL)), Self::THB => Ok(dirval!(PaymentCurrency = THB)), + Self::TJS => Ok(dirval!(PaymentCurrency = TJS)), + Self::TMT => Ok(dirval!(PaymentCurrency = TMT)), Self::TND => Ok(dirval!(PaymentCurrency = TND)), Self::TOP => Ok(dirval!(PaymentCurrency = TOP)), Self::TRY => Ok(dirval!(PaymentCurrency = TRY)), @@ -477,6 +496,7 @@ impl IntoDirValue for api_enums::Currency { Self::YER => Ok(dirval!(PaymentCurrency = YER)), Self::ZAR => Ok(dirval!(PaymentCurrency = ZAR)), Self::ZMW => Ok(dirval!(PaymentCurrency = ZMW)), + Self::ZWL => Ok(dirval!(PaymentCurrency = ZWL)), } } } diff --git a/crates/kgraph_utils/src/types.rs b/crates/kgraph_utils/src/types.rs index 26f27896e0a5..9ff55b68ab70 100644 --- a/crates/kgraph_utils/src/types.rs +++ b/crates/kgraph_utils/src/types.rs @@ -2,8 +2,8 @@ use std::collections::{HashMap, HashSet}; use api_models::enums as api_enums; use serde::Deserialize; -#[derive(Debug, Deserialize, Clone, Default)] +#[derive(Debug, Deserialize, Clone, Default)] pub struct CountryCurrencyFilter { pub connector_configs: HashMap, pub default_configs: Option, diff --git a/crates/masking/Cargo.toml b/crates/masking/Cargo.toml index ed20a08de343..003007989760 100644 --- a/crates/masking/Cargo.toml +++ b/crates/masking/Cargo.toml @@ -12,6 +12,7 @@ default = ["alloc", "serde", "diesel", "time"] alloc = ["zeroize/alloc"] serde = ["dep:serde", "dep:serde_json"] time = ["dep:time"] +cassandra = ["dep:scylla"] [package.metadata.docs.rs] all-features = true @@ -27,6 +28,7 @@ subtle = "2.5.0" time = { version = "0.3.35", optional = true, features = ["serde-human-readable"] } url = { version = "2.5.0", features = ["serde"] } zeroize = { version = "1.7", default-features = false } +scylla = { git = "https://github.com/juspay/scylla-rust-driver.git",rev = "5700aa2847b25437cdd4fcf34d707aa90dca8b89", optional = true} [dev-dependencies] serde_json = "1.0.115" diff --git a/crates/masking/src/abs.rs b/crates/masking/src/abs.rs index f50725d9f234..6501f89c9db0 100644 --- a/crates/masking/src/abs.rs +++ b/crates/masking/src/abs.rs @@ -1,6 +1,4 @@ -//! //! Abstract data types. -//! use crate::Secret; diff --git a/crates/masking/src/boxed.rs b/crates/masking/src/boxed.rs index 67397ec3c07f..42dda0873a13 100644 --- a/crates/masking/src/boxed.rs +++ b/crates/masking/src/boxed.rs @@ -1,4 +1,3 @@ -//! //! `Box` types containing secrets //! //! There is not alias type by design. diff --git a/crates/masking/src/cassandra.rs b/crates/masking/src/cassandra.rs new file mode 100644 index 000000000000..2544ab18a7aa --- /dev/null +++ b/crates/masking/src/cassandra.rs @@ -0,0 +1,40 @@ +use scylla::{ + deserialize::DeserializeValue, + frame::response::result::ColumnType, + serialize::{ + value::SerializeValue, + writers::{CellWriter, WrittenCellProof}, + SerializationError, + }, +}; + +use crate::{abs::PeekInterface, StrongSecret}; + +impl SerializeValue for StrongSecret +where + T: SerializeValue + zeroize::Zeroize + Clone, +{ + fn serialize<'b>( + &self, + typ: &ColumnType<'_>, + writer: CellWriter<'b>, + ) -> Result, SerializationError> { + self.peek().serialize(typ, writer) + } +} + +impl<'frame, 'metadata, T> DeserializeValue<'frame, 'metadata> for StrongSecret +where + T: DeserializeValue<'frame, 'metadata> + zeroize::Zeroize + Clone, +{ + fn type_check(typ: &ColumnType<'_>) -> Result<(), scylla::deserialize::TypeCheckError> { + T::type_check(typ) + } + + fn deserialize( + typ: &'metadata ColumnType<'metadata>, + v: Option>, + ) -> Result { + Ok(Self::new(T::deserialize(typ, v)?)) + } +} diff --git a/crates/masking/src/diesel.rs b/crates/masking/src/diesel.rs index ea60a861c9d5..148e50ed823a 100644 --- a/crates/masking/src/diesel.rs +++ b/crates/masking/src/diesel.rs @@ -1,6 +1,4 @@ -//! //! Diesel-related. -//! use diesel::{ backend::Backend, @@ -13,7 +11,7 @@ use diesel::{ use crate::{Secret, Strategy, StrongSecret, ZeroizableSecret}; -impl<'expr, S, I, T> AsExpression for &'expr Secret +impl AsExpression for &Secret where T: sql_types::SingleValue, I: Strategy, @@ -24,7 +22,7 @@ where } } -impl<'expr2, 'expr, S, I, T> AsExpression for &'expr2 &'expr Secret +impl AsExpression for &&Secret where T: sql_types::SingleValue, I: Strategy, @@ -81,7 +79,7 @@ where } } -impl<'expr, S, I, T> AsExpression for &'expr StrongSecret +impl AsExpression for &StrongSecret where T: sql_types::SingleValue, S: ZeroizableSecret, @@ -93,7 +91,7 @@ where } } -impl<'expr2, 'expr, S, I, T> AsExpression for &'expr2 &'expr StrongSecret +impl AsExpression for &&StrongSecret where T: sql_types::SingleValue, S: ZeroizableSecret, diff --git a/crates/masking/src/lib.rs b/crates/masking/src/lib.rs index ca0da6b67672..49e9cbf54f79 100644 --- a/crates/masking/src/lib.rs +++ b/crates/masking/src/lib.rs @@ -2,10 +2,8 @@ #![cfg_attr(docsrs, doc(cfg_hide(doc)))] #![warn(missing_docs)] -//! //! Personal Identifiable Information protection. Wrapper types and traits for secret management which help ensure they aren't accidentally copied, logged, or otherwise exposed (as much as possible), and also ensure secrets are securely wiped from memory when dropped. //! Secret-keeping library inspired by secrecy. -//! #![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR" ), "/", "README.md"))] @@ -49,7 +47,6 @@ pub use crate::serde::{ /// This module should be included with asterisk. /// /// `use masking::prelude::*;` -/// pub mod prelude { pub use super::{ExposeInterface, ExposeOptionInterface, PeekInterface}; } @@ -57,6 +54,9 @@ pub mod prelude { #[cfg(feature = "diesel")] mod diesel; +#[cfg(feature = "cassandra")] +mod cassandra; + pub mod maskable; pub use maskable::*; diff --git a/crates/masking/src/maskable.rs b/crates/masking/src/maskable.rs index 7969c9ab8e42..e957e89b3512 100644 --- a/crates/masking/src/maskable.rs +++ b/crates/masking/src/maskable.rs @@ -1,13 +1,9 @@ -//! //! This module contains Masking objects and traits -//! use crate::{ExposeInterface, Secret}; -/// /// An Enum that allows us to optionally mask data, based on which enum variant that data is stored /// in. -/// #[derive(Clone, Eq, PartialEq)] pub enum Maskable { /// Variant which masks the data by wrapping in a Secret @@ -35,9 +31,7 @@ impl std::hash::Hash for Maskable Maskable { - /// /// Get the inner data while consuming self - /// pub fn into_inner(self) -> T { match self { Self::Masked(inner_secret) => inner_secret.expose(), @@ -45,49 +39,37 @@ impl Maskable { } } - /// /// Create a new Masked data - /// pub fn new_masked(item: Secret) -> Self { Self::Masked(item) } - /// /// Create a new non-masked data - /// pub fn new_normal(item: T) -> Self { Self::Normal(item) } - /// /// Checks whether the data is masked. /// Returns `true` if the data is wrapped in the `Masked` variant, /// returns `false` otherwise. - /// pub fn is_masked(&self) -> bool { matches!(self, Self::Masked(_)) } - /// /// Checks whether the data is normal (not masked). /// Returns `true` if the data is wrapped in the `Normal` variant, /// returns `false` otherwise. - /// pub fn is_normal(&self) -> bool { matches!(self, Self::Normal(_)) } } /// Trait for providing a method on custom types for constructing `Maskable` - pub trait Mask { /// The type returned by the `into_masked()` method. Must implement `PartialEq`, `Eq` and `Clone` - type Output: Eq + Clone + PartialEq; - /// /// Construct a `Maskable` instance that wraps `Self::Output` by consuming `self` - /// fn into_masked(self) -> Maskable; } diff --git a/crates/masking/src/secret.rs b/crates/masking/src/secret.rs index a813829d63de..0bd28c3af926 100644 --- a/crates/masking/src/secret.rs +++ b/crates/masking/src/secret.rs @@ -1,12 +1,9 @@ -//! //! Structure describing secret. -//! use std::{fmt, marker::PhantomData}; use crate::{strategy::Strategy, PeekInterface, StrongSecret}; -/// /// Secret thing. /// /// To get access to value use method `expose()` of trait [`crate::ExposeInterface`]. @@ -39,7 +36,6 @@ use crate::{strategy::Strategy, PeekInterface, StrongSecret}; /// /// assert_eq!("hello", &format!("{:?}", my_secret)); /// ``` -/// pub struct Secret where MaskingStrategy: Strategy, diff --git a/crates/masking/src/serde.rs b/crates/masking/src/serde.rs index 0c5782ae04d5..48514df8c6c0 100644 --- a/crates/masking/src/serde.rs +++ b/crates/masking/src/serde.rs @@ -1,6 +1,4 @@ -//! //! Serde-related. -//! pub use erased_serde::Serialize as ErasedSerialize; pub use serde::{de, Deserialize, Serialize, Serializer}; @@ -17,7 +15,6 @@ use crate::{Secret, Strategy, StrongSecret, ZeroizableSecret}; /// /// This is done deliberately to prevent accidental exfiltration of secrets /// via `serde` serialization. -/// #[cfg_attr(docsrs, cfg(feature = "serde"))] pub trait SerializableSecret: Serialize {} @@ -87,7 +84,6 @@ where } } -/// /// Masked serialization. /// /// the default behaviour for secrets is to serialize in exposed format since the common use cases @@ -99,7 +95,6 @@ pub fn masked_serialize(value: &T) -> Result ErasedMaskSerialize for T { } } -impl<'a> Serialize for dyn ErasedMaskSerialize + 'a { +impl Serialize for dyn ErasedMaskSerialize + '_ { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -127,7 +122,7 @@ impl<'a> Serialize for dyn ErasedMaskSerialize + 'a { } } -impl<'a> Serialize for dyn ErasedMaskSerialize + 'a + Send { +impl Serialize for dyn ErasedMaskSerialize + '_ + Send { fn serialize(&self, serializer: S) -> Result where S: Serializer, diff --git a/crates/masking/src/strategy.rs b/crates/masking/src/strategy.rs index eb705ca490a7..b497cc3ed4f4 100644 --- a/crates/masking/src/strategy.rs +++ b/crates/masking/src/strategy.rs @@ -8,7 +8,7 @@ pub trait Strategy { /// Debug with type #[cfg_attr(feature = "serde", derive(serde::Deserialize))] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub enum WithType {} impl Strategy for WithType { diff --git a/crates/masking/src/string.rs b/crates/masking/src/string.rs index 2638fbd282ef..be6e90a21555 100644 --- a/crates/masking/src/string.rs +++ b/crates/masking/src/string.rs @@ -1,4 +1,3 @@ -//! //! Secret strings //! //! There is not alias type by design. diff --git a/crates/masking/src/strong_secret.rs b/crates/masking/src/strong_secret.rs index 51c0f2cb3fef..300b5463d250 100644 --- a/crates/masking/src/strong_secret.rs +++ b/crates/masking/src/strong_secret.rs @@ -1,6 +1,4 @@ -//! //! Structure describing secret. -//! use std::{fmt, marker::PhantomData}; @@ -9,11 +7,9 @@ use zeroize::{self, Zeroize as ZeroizableSecret}; use crate::{strategy::Strategy, PeekInterface}; -/// /// Secret thing. /// /// To get access to value use method `expose()` of trait [`crate::ExposeInterface`]. -/// pub struct StrongSecret { /// Inner secret value pub(crate) inner_secret: Secret, diff --git a/crates/masking/src/vec.rs b/crates/masking/src/vec.rs index 1f8c1c671e0b..2a077be99439 100644 --- a/crates/masking/src/vec.rs +++ b/crates/masking/src/vec.rs @@ -1,4 +1,3 @@ -//! //! Secret `Vec` types //! //! There is not alias type by design. diff --git a/crates/openapi/Cargo.toml b/crates/openapi/Cargo.toml index 6aae926011a0..7e7e5a6fb3c4 100644 --- a/crates/openapi/Cargo.toml +++ b/crates/openapi/Cargo.toml @@ -12,12 +12,13 @@ utoipa = { version = "4.2.0", features = ["preserve_order", "preserve_path_order # First party crates api_models = { version = "0.1.0", path = "../api_models", features = ["frm", "payouts", "openapi"] } -common_utils = { version = "0.1.0", path = "../common_utils" } +common_utils = { version = "0.1.0", path = "../common_utils", features = ["logs"] } +common_types = { version = "0.1.0", path = "../common_types" } router_env = { version = "0.1.0", path = "../router_env" } [features] -v2 = ["api_models/v2", "api_models/customer_v2"] -v1 = ["api_models/v1"] +v2 = ["api_models/v2", "api_models/customer_v2", "common_utils/v2", "api_models/payment_methods_v2", "common_utils/payment_methods_v2"] +v1 = ["api_models/v1", "common_utils/v1"] [lints] workspace = true diff --git a/crates/openapi/src/main.rs b/crates/openapi/src/main.rs index 9d58c8b7c0d6..975ff33107fa 100644 --- a/crates/openapi/src/main.rs +++ b/crates/openapi/src/main.rs @@ -6,6 +6,9 @@ mod routes; #[allow(clippy::print_stdout)] // Using a logger is not necessary here fn main() { + #[cfg(all(feature = "v1", feature = "v2"))] + compile_error!("features v1 and v2 are mutually exclusive, please enable only one of them"); + #[cfg(feature = "v1")] let relative_file_path = "api-reference/openapi_spec.json"; diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index a76726eb8ac7..2f068b5609b8 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -82,6 +82,11 @@ Never share your secret api keys. Keep them guarded and secure. routes::payment_link::payment_link_retrieve, routes::payments::payments_external_authentication, routes::payments::payments_complete_authorize, + routes::payments::payments_post_session_tokens, + + // Routes for relay + routes::relay, + routes::relay_retrieve, // Routes for refunds routes::refunds::refunds_create, @@ -158,8 +163,9 @@ Never share your secret api keys. Keep them guarded and secure. routes::routing::routing_retrieve_linked_config, routes::routing::routing_retrieve_default_config_for_profiles, routes::routing::routing_update_default_config_for_profile, - routes::routing::toggle_success_based_routing, routes::routing::success_based_routing_update_configs, + routes::routing::toggle_success_based_routing, + routes::routing::toggle_elimination_routing, // Routes for blocklist routes::blocklist::remove_entry_from_blocklist, @@ -208,6 +214,13 @@ Never share your secret api keys. Keep them guarded and secure. common_utils::payout_method_utils::PixBankTransferAdditionalData, common_utils::payout_method_utils::PaypalAdditionalData, common_utils::payout_method_utils::VenmoAdditionalData, + common_types::payments::SplitPaymentsRequest, + common_types::payments::StripeSplitPaymentRequest, + common_utils::types::ChargeRefunds, + common_types::refunds::SplitRefund, + common_types::refunds::StripeSplitRefundRequest, + api_models::payments::SplitPaymentsResponse, + api_models::payments::StripeSplitPaymentsResponse, api_models::refunds::RefundRequest, api_models::refunds::RefundType, api_models::refunds::RefundResponse, @@ -228,12 +241,18 @@ Never share your secret api keys. Keep them guarded and secure. api_models::admin::BusinessCollectLinkConfig, api_models::admin::BusinessPayoutLinkConfig, api_models::customers::CustomerRequest, + api_models::customers::CustomerUpdateRequest, api_models::customers::CustomerDeleteResponse, api_models::payment_methods::PaymentMethodCreate, api_models::payment_methods::PaymentMethodResponse, - api_models::payment_methods::PaymentMethodList, api_models::payment_methods::CustomerPaymentMethod, api_models::payment_methods::PaymentMethodListResponse, + api_models::payment_methods::ResponsePaymentMethodsEnabled, + api_models::payment_methods::ResponsePaymentMethodTypes, + api_models::payment_methods::PaymentExperienceTypes, + api_models::payment_methods::CardNetworkTypes, + api_models::payment_methods::BankDebitTypes, + api_models::payment_methods::BankTransferTypes, api_models::payment_methods::CustomerPaymentMethodsListResponse, api_models::payment_methods::PaymentMethodDeleteResponse, api_models::payment_methods::PaymentMethodUpdate, @@ -249,6 +268,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::admin::AcceptedCountries, api_models::admin::AcceptedCurrencies, api_models::enums::PaymentType, + api_models::enums::ScaExemptionType, api_models::enums::PaymentMethod, api_models::enums::PaymentMethodType, api_models::enums::ConnectorType, @@ -280,8 +300,14 @@ Never share your secret api keys. Keep them guarded and secure. api_models::enums::ReconStatus, api_models::enums::ConnectorStatus, api_models::enums::AuthorizationStatus, + api_models::enums::ElementPosition, + api_models::enums::ElementSize, + api_models::enums::SizeVariants, + api_models::enums::PaymentLinkDetailsLayout, api_models::enums::PaymentMethodStatus, api_models::enums::UIWidgetFormLayout, + api_models::enums::PaymentConnectorCategory, + api_models::enums::FeatureStatus, api_models::admin::MerchantConnectorCreate, api_models::admin::AdditionalMerchantData, api_models::admin::ConnectorWalletDetails, @@ -299,6 +325,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::admin::ProfileCreate, api_models::admin::ProfileResponse, api_models::admin::BusinessPaymentLinkConfig, + api_models::admin::PaymentLinkBackgroundImageConfig, api_models::admin::PaymentLinkConfigRequest, api_models::admin::PaymentLinkConfig, api_models::admin::PaymentLinkTransactionDetails, @@ -360,7 +387,6 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::SwishQrData, api_models::payments::AirwallexData, api_models::payments::NoonData, - api_models::payments::OrderDetails, api_models::payments::OrderDetailsWithAmount, api_models::payments::NextActionType, api_models::payments::WalletData, @@ -389,6 +415,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::PaymentsCaptureRequest, api_models::payments::PaymentsSessionRequest, api_models::payments::PaymentsSessionResponse, + api_models::payments::PazeWalletData, api_models::payments::SessionToken, api_models::payments::ApplePaySessionResponse, api_models::payments::ThirdPartySdkSessionResponse, @@ -397,9 +424,16 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::ApplePayPaymentRequest, api_models::payments::ApplePayBillingContactFields, api_models::payments::ApplePayShippingContactFields, + api_models::payments::ApplePayRecurringPaymentRequest, + api_models::payments::ApplePayRegularBillingRequest, + api_models::payments::ApplePayPaymentTiming, + api_models::payments::RecurringPaymentIntervalUnit, + api_models::payments::ApplePayRecurringDetails, + api_models::payments::ApplePayRegularBillingDetails, api_models::payments::ApplePayAddressParameters, api_models::payments::AmountInfo, - api_models::payments::ProductType, + api_models::payments::ClickToPaySessionResponse, + api_models::enums::ProductType, api_models::payments::GooglePayWalletData, api_models::payments::PayPalWalletData, api_models::payments::PaypalRedirection, @@ -423,6 +457,9 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::GooglePayPaymentMethodInfo, api_models::payments::ApplePayWalletData, api_models::payments::SamsungPayWalletCredentials, + api_models::payments::SamsungPayWebWalletData, + api_models::payments::SamsungPayAppWalletData, + api_models::payments::SamsungPayCardBrand, api_models::payments::SamsungPayTokenData, api_models::payments::ApplepayPaymentMethod, api_models::payments::PaymentsCancelRequest, @@ -436,6 +473,8 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::MultibancoBillingDetails, api_models::payments::DokuBillingDetails, api_models::payments::BankTransferInstructions, + api_models::payments::MobilePaymentNextStepData, + api_models::payments::MobilePaymentConsent, api_models::payments::ReceiverDetails, api_models::payments::AchTransfer, api_models::payments::MultibancoTransferInstructions, @@ -445,6 +484,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::GooglePayRedirectData, api_models::payments::GooglePayThirdPartySdk, api_models::payments::GooglePaySessionResponse, + api_models::payments::PazeSessionTokenResponse, api_models::payments::SamsungPaySessionTokenResponse, api_models::payments::SamsungPayMerchantPaymentInformation, api_models::payments::SamsungPayAmountDetails, @@ -486,18 +526,28 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payment_methods::PaymentMethodCollectLinkResponse, api_models::refunds::RefundListRequest, api_models::refunds::RefundListResponse, - api_models::refunds::RefundAggregateResponse, + api_models::relay::RelayRequest, + api_models::relay::RelayResponse, + api_models::enums::RelayType, + api_models::relay::RelayData, + api_models::relay::RelayRefundRequest, + api_models::enums::RelayStatus, + api_models::relay::RelayError, api_models::payments::AmountFilter, api_models::mandates::MandateRevokedResponse, api_models::mandates::MandateResponse, api_models::mandates::MandateCardDetails, api_models::mandates::RecurringDetails, + api_models::mandates::NetworkTransactionIdAndCardDetails, api_models::mandates::ProcessorPaymentToken, api_models::ephemeral_key::EphemeralKeyCreateResponse, api_models::payments::CustomerDetails, api_models::payments::GiftCardData, api_models::payments::GiftCardDetails, + api_models::payments::MobilePaymentData, + api_models::payments::MobilePaymentResponse, api_models::payments::Address, + api_models::payments::BankCodeResponse, api_models::payouts::CardPayout, api_models::payouts::Wallet, api_models::payouts::Paypal, @@ -559,6 +609,11 @@ Never share your secret api keys. Keep them guarded and secure. api_models::routing::RoutingDictionaryRecord, api_models::routing::RoutingKind, api_models::routing::RoutableConnectorChoice, + api_models::routing::DynamicRoutingFeatures, + api_models::routing::SuccessBasedRoutingConfig, + api_models::routing::DynamicRoutingConfigParams, + api_models::routing::CurrentBlockThreshold, + api_models::routing::SuccessBasedRoutingConfigBody, api_models::routing::LinkedRoutingConfigRetrieveResponse, api_models::routing::RoutingRetrieveResponse, api_models::routing::ProfileDefaultRoutingConfig, @@ -569,13 +624,8 @@ Never share your secret api keys. Keep them guarded and secure. api_models::routing::StraightThroughAlgorithm, api_models::routing::ConnectorVolumeSplit, api_models::routing::ConnectorSelection, - api_models::routing::ToggleSuccessBasedRoutingQuery, - api_models::routing::SuccessBasedRoutingConfig, - api_models::routing::SuccessBasedRoutingConfigParams, - api_models::routing::SuccessBasedRoutingConfigBody, - api_models::routing::CurrentBlockThreshold, - api_models::routing::SuccessBasedRoutingUpdateConfigQuery, - api_models::routing::ToggleSuccessBasedRoutingPath, + api_models::routing::ToggleDynamicRoutingQuery, + api_models::routing::ToggleDynamicRoutingPath, api_models::routing::ast::RoutableChoiceKind, api_models::enums::RoutableConnectors, api_models::routing::ast::ProgramConnectorSelection, @@ -593,6 +643,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::blocklist::ToggleBlocklistResponse, api_models::blocklist::ListBlocklistQuery, api_models::enums::BlocklistDataKind, + api_models::enums::ErrorCategory, api_models::webhook_events::EventListItemResponse, api_models::webhook_events::EventRetrieveResponse, api_models::webhook_events::OutgoingWebhookRequestContent, @@ -600,9 +651,6 @@ Never share your secret api keys. Keep them guarded and secure. api_models::enums::WebhookDeliveryAttempt, api_models::enums::PaymentChargeType, api_models::enums::StripeChargeType, - api_models::payments::PaymentChargeRequest, - api_models::payments::PaymentChargeResponse, - api_models::refunds::ChargeRefunds, api_models::payments::CustomerDetailsResponse, api_models::payments::OpenBankingData, api_models::payments::OpenBankingSessionToken, @@ -640,6 +688,13 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::WalletResponseData, api_models::payments::PaymentsDynamicTaxCalculationResponse, api_models::payments::DisplayAmountOnSdk, + api_models::payments::PaymentsPostSessionTokensRequest, + api_models::payments::PaymentsPostSessionTokensResponse, + api_models::payments::CtpServiceDetails, + api_models::feature_matrix::FeatureMatrixListResponse, + api_models::feature_matrix::FeatureMatrixRequest, + api_models::feature_matrix::ConnectorFeatureMatrixResponse, + api_models::feature_matrix::SupportedPaymentMethod, )), modifiers(&SecurityAddon) )] diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index 715af43defdb..12b0fd003559 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -120,10 +120,31 @@ Never share your secret api keys. Keep them guarded and secure. //Routes for payments routes::payments::payments_create_intent, + routes::payments::payments_get_intent, + routes::payments::payments_update_intent, + routes::payments::payments_confirm_intent, + routes::payments::payment_status, + routes::payments::payments_connector_session, + routes::payments::list_payment_methods, + + //Routes for payment methods + routes::payment_method::list_customer_payment_method_for_payment, + routes::payment_method::list_customer_payment_method_api, + routes::payment_method::create_payment_method_api, + routes::payment_method::create_payment_method_intent_api, + routes::payment_method::confirm_payment_method_intent_api, + routes::payment_method::payment_method_update_api, + routes::payment_method::payment_method_retrieve_api, + routes::payment_method::payment_method_delete_api, + + + //Routes for refunds + routes::refunds::refunds_create, ), components(schemas( common_utils::types::MinorUnit, common_utils::types::TimeRange, + common_utils::types::BrowserInformation, common_utils::link_utils::GenericLinkUiConfig, common_utils::link_utils::EnabledPaymentMethod, common_utils::payout_method_utils::AdditionalPayoutMethodData, @@ -136,7 +157,17 @@ Never share your secret api keys. Keep them guarded and secure. common_utils::payout_method_utils::PixBankTransferAdditionalData, common_utils::payout_method_utils::PaypalAdditionalData, common_utils::payout_method_utils::VenmoAdditionalData, + common_types::payments::SplitPaymentsRequest, + common_types::payments::StripeSplitPaymentRequest, + common_types::refunds::StripeSplitRefundRequest, + common_utils::types::ChargeRefunds, + common_types::payment_methods::PaymentMethodsEnabled, + common_types::refunds::SplitRefund, + api_models::payments::SplitPaymentsResponse, + api_models::payments::StripeSplitPaymentsResponse, api_models::refunds::RefundRequest, + api_models::refunds::RefundsCreateRequest, + api_models::refunds::RefundErrorDetails, api_models::refunds::RefundType, api_models::refunds::RefundResponse, api_models::refunds::RefundStatus, @@ -144,7 +175,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::organization::OrganizationCreateRequest, api_models::organization::OrganizationUpdateRequest, api_models::organization::OrganizationResponse, - api_models::admin::MerchantAccountCreate, + api_models::admin::MerchantAccountCreateWithoutOrgId, api_models::admin::MerchantAccountUpdate, api_models::admin::MerchantAccountDeleteResponse, api_models::admin::MerchantConnectorDeleteResponse, @@ -156,27 +187,44 @@ Never share your secret api keys. Keep them guarded and secure. api_models::admin::BusinessCollectLinkConfig, api_models::admin::BusinessPayoutLinkConfig, api_models::customers::CustomerRequest, + api_models::customers::CustomerUpdateRequest, api_models::customers::CustomerDeleteResponse, api_models::payment_methods::PaymentMethodCreate, + api_models::payment_methods::PaymentMethodIntentCreate, + api_models::payment_methods::PaymentMethodIntentConfirm, api_models::payment_methods::PaymentMethodResponse, - api_models::payment_methods::PaymentMethodList, + api_models::payment_methods::PaymentMethodResponseData, api_models::payment_methods::CustomerPaymentMethod, + api_models::payment_methods::PaymentMethodListRequest, api_models::payment_methods::PaymentMethodListResponse, + api_models::payment_methods::ResponsePaymentMethodsEnabled, + api_models::payment_methods::PaymentMethodSubtypeSpecificData, + api_models::payment_methods::ResponsePaymentMethodTypes, + api_models::payment_methods::PaymentExperienceTypes, + api_models::payment_methods::CardNetworkTypes, + api_models::payment_methods::BankDebitTypes, + api_models::payment_methods::BankTransferTypes, api_models::payment_methods::CustomerPaymentMethodsListResponse, api_models::payment_methods::PaymentMethodDeleteResponse, api_models::payment_methods::PaymentMethodUpdate, + api_models::payment_methods::PaymentMethodUpdateData, api_models::payment_methods::CustomerDefaultPaymentMethodResponse, api_models::payment_methods::CardDetailFromLocker, api_models::payment_methods::PaymentMethodCreateData, api_models::payment_methods::CardDetail, api_models::payment_methods::CardDetailUpdate, + api_models::payment_methods::CardType, api_models::payment_methods::RequestPaymentMethodTypes, + api_models::payment_methods::CardType, + api_models::payment_methods::PaymentMethodListData, api_models::poll::PollResponse, api_models::poll::PollStatus, api_models::customers::CustomerResponse, api_models::admin::AcceptedCountries, api_models::admin::AcceptedCurrencies, + api_models::enums::ProductType, api_models::enums::PaymentType, + api_models::enums::ScaExemptionType, api_models::enums::PaymentMethod, api_models::enums::PaymentMethodType, api_models::enums::ConnectorType, @@ -208,7 +256,13 @@ Never share your secret api keys. Keep them guarded and secure. api_models::enums::ReconStatus, api_models::enums::ConnectorStatus, api_models::enums::AuthorizationStatus, + api_models::enums::ElementPosition, + api_models::enums::ElementSize, + api_models::enums::SizeVariants, + api_models::enums::PaymentLinkDetailsLayout, api_models::enums::PaymentMethodStatus, + api_models::enums::PaymentConnectorCategory, + api_models::enums::FeatureStatus, api_models::enums::OrderFulfillmentTimeOrigin, api_models::enums::UIWidgetFormLayout, api_models::admin::MerchantConnectorCreate, @@ -221,13 +275,13 @@ Never share your secret api keys. Keep them guarded and secure. api_models::admin::FrmConfigs, api_models::admin::FrmPaymentMethod, api_models::admin::FrmPaymentMethodType, - api_models::admin::PaymentMethodsEnabled, api_models::admin::MerchantConnectorDetailsWrap, api_models::admin::MerchantConnectorDetails, api_models::admin::MerchantConnectorWebhookDetails, api_models::admin::ProfileCreate, api_models::admin::ProfileResponse, api_models::admin::BusinessPaymentLinkConfig, + api_models::admin::PaymentLinkBackgroundImageConfig, api_models::admin::PaymentLinkConfigRequest, api_models::admin::PaymentLinkConfig, api_models::admin::PaymentLinkTransactionDetails, @@ -289,7 +343,6 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::SwishQrData, api_models::payments::AirwallexData, api_models::payments::NoonData, - api_models::payments::OrderDetails, api_models::payments::OrderDetailsWithAmount, api_models::payments::NextActionType, api_models::payments::WalletData, @@ -307,10 +360,6 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::CardRedirectData, api_models::payments::CardToken, api_models::payments::CustomerAcceptance, - api_models::payments::PaymentsRequest, - api_models::payments::PaymentsCreateRequest, - api_models::payments::PaymentsUpdateRequest, - api_models::payments::PaymentsConfirmRequest, api_models::payments::PaymentsResponse, api_models::payments::PaymentsCreateResponseOpenApi, api_models::payments::PaymentRetrieveBody, @@ -319,8 +368,11 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::PaymentsSessionRequest, api_models::payments::PaymentsSessionResponse, api_models::payments::PaymentsCreateIntentRequest, - api_models::payments::PaymentsCreateIntentResponse, + api_models::payments::PaymentsUpdateIntentRequest, + api_models::payments::PaymentsIntentResponse, + api_models::payments::PazeWalletData, api_models::payments::AmountDetails, + api_models::payments::AmountDetailsUpdate, api_models::payments::SessionToken, api_models::payments::ApplePaySessionResponse, api_models::payments::ThirdPartySdkSessionResponse, @@ -330,8 +382,13 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::ApplePayBillingContactFields, api_models::payments::ApplePayShippingContactFields, api_models::payments::ApplePayAddressParameters, + api_models::payments::ApplePayRecurringPaymentRequest, + api_models::payments::ApplePayRegularBillingRequest, + api_models::payments::ApplePayPaymentTiming, + api_models::payments::RecurringPaymentIntervalUnit, + api_models::payments::ApplePayRecurringDetails, + api_models::payments::ApplePayRegularBillingDetails, api_models::payments::AmountInfo, - api_models::payments::ProductType, api_models::payments::GooglePayWalletData, api_models::payments::PayPalWalletData, api_models::payments::PaypalRedirection, @@ -355,12 +412,16 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::GooglePayPaymentMethodInfo, api_models::payments::ApplePayWalletData, api_models::payments::ApplepayPaymentMethod, + api_models::payments::PazeSessionTokenResponse, api_models::payments::SamsungPaySessionTokenResponse, api_models::payments::SamsungPayMerchantPaymentInformation, api_models::payments::SamsungPayAmountDetails, api_models::payments::SamsungPayAmountFormat, api_models::payments::SamsungPayProtocolType, api_models::payments::SamsungPayWalletCredentials, + api_models::payments::SamsungPayWebWalletData, + api_models::payments::SamsungPayAppWalletData, + api_models::payments::SamsungPayCardBrand, api_models::payments::SamsungPayTokenData, api_models::payments::PaymentsCancelRequest, api_models::payments::PaymentListConstraints, @@ -373,6 +434,8 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::MultibancoBillingDetails, api_models::payments::DokuBillingDetails, api_models::payments::BankTransferInstructions, + api_models::payments::MobilePaymentNextStepData, + api_models::payments::MobilePaymentConsent, api_models::payments::ReceiverDetails, api_models::payments::AchTransfer, api_models::payments::MultibancoTransferInstructions, @@ -381,6 +444,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::ApplePayThirdPartySdkData, api_models::payments::GooglePayRedirectData, api_models::payments::GooglePayThirdPartySdk, + api_models::mandates::NetworkTransactionIdAndCardDetails, api_models::payments::GooglePaySessionResponse, api_models::payments::GpayShippingAddressParameters, api_models::payments::GpayBillingAddressParameters, @@ -400,14 +464,19 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::DeviceChannel, api_models::payments::ThreeDsCompletionIndicator, api_models::payments::MifinityData, + api_models::payments::ClickToPaySessionResponse, api_models::enums::TransactionStatus, - api_models::payments::BrowserInformation, api_models::payments::PaymentCreatePaymentLinkConfig, api_models::payments::ThreeDsData, api_models::payments::ThreeDsMethodData, api_models::payments::PollConfigResponse, api_models::payments::ExternalAuthenticationDetailsResponse, api_models::payments::ExtendedCardInfo, + api_models::payments::PaymentsConfirmIntentRequest, + api_models::payments::PaymentsConfirmIntentResponse, + api_models::payments::AmountDetailsResponse, + api_models::payments::BankCodeResponse, + api_models::payments::PaymentMethodListResponseForPayments, api_models::payment_methods::RequiredFieldInfo, api_models::payment_methods::DefaultPaymentMethod, api_models::payment_methods::MaskedBankDetails, @@ -416,9 +485,9 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payment_methods::SurchargePercentage, api_models::payment_methods::PaymentMethodCollectLinkRequest, api_models::payment_methods::PaymentMethodCollectLinkResponse, + api_models::payments::PaymentsRetrieveResponse, api_models::refunds::RefundListRequest, api_models::refunds::RefundListResponse, - api_models::refunds::RefundAggregateResponse, api_models::payments::AmountFilter, api_models::mandates::MandateRevokedResponse, api_models::mandates::MandateResponse, @@ -429,6 +498,8 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::CustomerDetails, api_models::payments::GiftCardData, api_models::payments::GiftCardDetails, + api_models::payments::MobilePaymentData, + api_models::payments::MobilePaymentResponse, api_models::payments::Address, api_models::payouts::CardPayout, api_models::payouts::Wallet, @@ -523,6 +594,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::blocklist::ToggleBlocklistResponse, api_models::blocklist::ListBlocklistQuery, api_models::enums::BlocklistDataKind, + api_models::enums::ErrorCategory, api_models::webhook_events::EventListItemResponse, api_models::webhook_events::EventRetrieveResponse, api_models::webhook_events::OutgoingWebhookRequestContent, @@ -530,9 +602,6 @@ Never share your secret api keys. Keep them guarded and secure. api_models::enums::WebhookDeliveryAttempt, api_models::enums::PaymentChargeType, api_models::enums::StripeChargeType, - api_models::payments::PaymentChargeRequest, - api_models::payments::PaymentChargeResponse, - api_models::refunds::ChargeRefunds, api_models::payments::CustomerDetailsResponse, api_models::payments::OpenBankingData, api_models::payments::OpenBankingSessionToken, @@ -570,6 +639,15 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::PaymentsDynamicTaxCalculationRequest, api_models::payments::PaymentsDynamicTaxCalculationResponse, api_models::payments::DisplayAmountOnSdk, + api_models::payments::ErrorDetails, + api_models::payments::CtpServiceDetails, + api_models::feature_matrix::FeatureMatrixListResponse, + api_models::feature_matrix::FeatureMatrixRequest, + api_models::feature_matrix::ConnectorFeatureMatrixResponse, + api_models::feature_matrix::SupportedPaymentMethod, + common_utils::types::BrowserInformation, + api_models::payments::PaymentAmountDetailsResponse, + routes::payments::ForceSync, )), modifiers(&SecurityAddon) )] @@ -597,7 +675,7 @@ impl utoipa::Modify for SecurityAddon { SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::with_description( "api-key", "Admin API keys allow you to perform some privileged actions such as \ - creating a merchant account and Merchant Connector account." + creating a merchant account and Connector account." ))), ), ( diff --git a/crates/openapi/src/routes.rs b/crates/openapi/src/routes.rs index 453f7f557d34..2c1c58244124 100644 --- a/crates/openapi/src/routes.rs +++ b/crates/openapi/src/routes.rs @@ -16,10 +16,11 @@ pub mod payouts; pub mod poll; pub mod profile; pub mod refunds; +pub mod relay; pub mod routing; pub mod webhook_events; pub use self::{ customers::*, mandates::*, merchant_account::*, merchant_connector_account::*, organization::*, - payment_method::*, payments::*, poll::*, refunds::*, routing::*, webhook_events::*, + payment_method::*, payments::*, poll::*, refunds::*, relay::*, routing::*, webhook_events::*, }; diff --git a/crates/openapi/src/routes/api_keys.rs b/crates/openapi/src/routes/api_keys.rs index 7527c8a10957..964fa60fcf59 100644 --- a/crates/openapi/src/routes/api_keys.rs +++ b/crates/openapi/src/routes/api_keys.rs @@ -25,7 +25,7 @@ pub async fn api_key_create() {} /// displayed only once on creation, so ensure you store it securely. #[utoipa::path( post, - path = "/v2/api_keys", + path = "/v2/api-keys", request_body= CreateApiKeyRequest, responses( (status = 200, description = "API Key created", body = CreateApiKeyResponse), @@ -64,9 +64,9 @@ pub async fn api_key_retrieve() {} /// Retrieve information about the specified API Key. #[utoipa::path( get, - path = "/v2/api_keys/{key_id}", + path = "/v2/api-keys/{id}", params ( - ("key_id" = String, Path, description = "The unique identifier for the API Key") + ("id" = String, Path, description = "The unique identifier for the API Key") ), responses( (status = 200, description = "API Key retrieved", body = RetrieveApiKeyResponse), @@ -106,10 +106,10 @@ pub async fn api_key_update() {} /// Update information for the specified API Key. #[utoipa::path( put, - path = "/v2/api_keys/{key_id}", + path = "/v2/api-keys/{id}", request_body = UpdateApiKeyRequest, params ( - ("key_id" = String, Path, description = "The unique identifier for the API Key") + ("id" = String, Path, description = "The unique identifier for the API Key") ), responses( (status = 200, description = "API Key updated", body = RetrieveApiKeyResponse), @@ -150,9 +150,9 @@ pub async fn api_key_revoke() {} /// authenticating with our APIs. #[utoipa::path( delete, - path = "/v2/api_keys/{key_id}", + path = "/v2/api-keys/{id}", params ( - ("key_id" = String, Path, description = "The unique identifier for the API Key") + ("id" = String, Path, description = "The unique identifier for the API Key") ), responses( (status = 200, description = "API Key revoked", body = RevokeApiKeyResponse), @@ -191,7 +191,7 @@ pub async fn api_key_list() {} /// List all the API Keys associated to a merchant account. #[utoipa::path( get, - path = "/v2/api_keys/list", + path = "/v2/api-keys/list", params( ("limit" = Option, Query, description = "The maximum number of API Keys to include in the response"), ("skip" = Option, Query, description = "The number of API Keys to skip when retrieving the list of API keys."), diff --git a/crates/openapi/src/routes/customers.rs b/crates/openapi/src/routes/customers.rs index b4c612307524..f23b8b349571 100644 --- a/crates/openapi/src/routes/customers.rs +++ b/crates/openapi/src/routes/customers.rs @@ -51,7 +51,7 @@ pub async fn customers_retrieve() {} post, path = "/customers/{customer_id}", request_body ( - content = CustomerRequest, + content = CustomerUpdateRequest, examples (( "Update name and email of a customer" =( value =json!( { "email": "guest@example.com", @@ -159,7 +159,7 @@ pub async fn customers_retrieve() {} post, path = "/v2/customers/{id}", request_body ( - content = CustomerRequest, + content = CustomerUpdateRequest, examples (( "Update name and email of a customer" =( value =json!( { "email": "guest@example.com", diff --git a/crates/openapi/src/routes/merchant_account.rs b/crates/openapi/src/routes/merchant_account.rs index b92285245a43..a3bf96ab897f 100644 --- a/crates/openapi/src/routes/merchant_account.rs +++ b/crates/openapi/src/routes/merchant_account.rs @@ -50,7 +50,14 @@ pub async fn merchant_account_create() {} /// Before creating the merchant account, it is mandatory to create an organization. #[utoipa::path( post, - path = "/v2/merchant_accounts", + path = "/v2/merchant-accounts", + params( + ( + "X-Organization-Id" = String, Header, + description = "Organization ID for which the merchant account has to be created.", + example = json!({"X-Organization-Id": "org_abcdefghijklmnop"}) + ), + ), request_body( content = MerchantAccountCreate, examples( @@ -58,7 +65,6 @@ pub async fn merchant_account_create() {} "Create a merchant account with minimal fields" = ( value = json!({ "merchant_name": "Cloth Store", - "organization_id": "org_abcdefghijklmnop" }) ) ), @@ -66,7 +72,6 @@ pub async fn merchant_account_create() {} "Create a merchant account with merchant details" = ( value = json!({ "merchant_name": "Cloth Store", - "organization_id": "org_abcdefghijklmnop", "merchant_details": { "primary_contact_person": "John Doe", "primary_email": "example@company.com" @@ -78,7 +83,6 @@ pub async fn merchant_account_create() {} "Create a merchant account with metadata" = ( value = json!({ "merchant_name": "Cloth Store", - "organization_id": "org_abcdefghijklmnop", "metadata": { "key_1": "John Doe", "key_2": "Trends" @@ -124,7 +128,7 @@ pub async fn retrieve_merchant_account() {} /// Retrieve a *merchant* account details. #[utoipa::path( get, - path = "/v2/merchant_accounts/{id}", + path = "/v2/merchant-accounts/{id}", params (("id" = String, Path, description = "The unique identifier for the merchant account")), responses( (status = 200, description = "Merchant Account Retrieved", body = MerchantAccountResponse), @@ -186,7 +190,7 @@ pub async fn update_merchant_account() {} /// Updates details of an existing merchant account. Helpful in updating merchant details such as email, contact details, or other configuration details like webhook, routing algorithm etc #[utoipa::path( put, - path = "/v2/merchant_accounts/{id}", + path = "/v2/merchant-accounts/{id}", request_body ( content = MerchantAccountUpdate, examples( @@ -208,7 +212,7 @@ pub async fn update_merchant_account() {} ) ), )), - params (("account_id" = String, Path, description = "The unique identifier for the merchant account")), + params (("id" = String, Path, description = "The unique identifier for the merchant account")), responses( (status = 200, description = "Merchant Account Updated", body = MerchantAccountResponse), (status = 404, description = "Merchant account not found") @@ -291,13 +295,13 @@ pub async fn merchant_account_kv_status() {} pub async fn payment_connector_list_profile() {} #[cfg(feature = "v2")] -/// Profile - List +/// Merchant Account - Profile List /// /// List profiles for an Merchant #[utoipa::path( get, - path = "/v2/merchant_accounts/{account_id}/profiles", - params (("account_id" = String, Path, description = "The unique identifier for the Merchant")), + path = "/v2/merchant-accounts/{id}/profiles", + params (("id" = String, Path, description = "The unique identifier for the Merchant")), responses( (status = 200, description = "profile list retrieved successfully", body = Vec), (status = 400, description = "Invalid data") diff --git a/crates/openapi/src/routes/merchant_connector_account.rs b/crates/openapi/src/routes/merchant_connector_account.rs index a9cabde8af4c..372f8688a26c 100644 --- a/crates/openapi/src/routes/merchant_connector_account.rs +++ b/crates/openapi/src/routes/merchant_connector_account.rs @@ -61,13 +61,13 @@ )] pub async fn connector_create() {} -/// Merchant Connector - Create +/// Connector Account - Create /// -/// Creates a new Merchant Connector for the merchant account. The connector could be a payment processor/facilitator/acquirer or a provider of specialized services like Fraud/Accounting etc. +/// Creates a new Connector Account for the merchant account. The connector could be a payment processor/facilitator/acquirer or a provider of specialized services like Fraud/Accounting etc. #[cfg(feature = "v2")] #[utoipa::path( post, - path = "/v2/connector_accounts", + path = "/v2/connector-accounts", request_body( content = MerchantConnectorCreate, examples( @@ -146,13 +146,13 @@ pub async fn connector_create() {} )] pub async fn connector_retrieve() {} -/// Merchant Connector - Retrieve +/// Connector Account - Retrieve /// /// Retrieves details of a Connector account #[cfg(feature = "v2")] #[utoipa::path( get, - path = "/v2/connector_accounts/{id}", + path = "/v2/connector-accounts/{id}", params( ("id" = i32, Path, description = "The unique identifier for the Merchant Connector") ), @@ -235,13 +235,13 @@ pub async fn connector_list() {} )] pub async fn connector_update() {} -/// Merchant Connector - Update +/// Connector Account - Update /// -/// To update an existing Merchant Connector account. Helpful in enabling/disabling different payment methods and other settings for the connector +/// To update an existing Connector account. Helpful in enabling/disabling different payment methods and other settings for the connector #[cfg(feature = "v2")] #[utoipa::path( put, - path = "/v2/connector_accounts/{id}", + path = "/v2/connector-accounts/{id}", request_body( content = MerchantConnectorUpdate, examples( @@ -310,7 +310,7 @@ pub async fn connector_delete() {} #[cfg(feature = "v2")] #[utoipa::path( delete, - path = "/v2/connector_accounts/{id}", + path = "/v2/connector-accounts/{id}", params( ("id" = i32, Path, description = "The unique identifier for the Merchant Connector") ), diff --git a/crates/openapi/src/routes/organization.rs b/crates/openapi/src/routes/organization.rs index 08ee56725ee3..d677131d5dba 100644 --- a/crates/openapi/src/routes/organization.rs +++ b/crates/openapi/src/routes/organization.rs @@ -31,8 +31,8 @@ pub async fn organization_create() {} /// Retrieve an existing organization #[utoipa::path( get, - path = "/organization/{organization_id}", - params (("organization_id" = String, Path, description = "The unique identifier for the Organization")), + path = "/organization/{id}", + params (("id" = String, Path, description = "The unique identifier for the Organization")), responses( (status = 200, description = "Organization Created", body =OrganizationResponse), (status = 400, description = "Invalid data") @@ -49,7 +49,7 @@ pub async fn organization_retrieve() {} /// Create a new organization for . #[utoipa::path( put, - path = "/organization/{organization_id}", + path = "/organization/{id}", request_body( content = OrganizationUpdateRequest, examples( @@ -60,7 +60,7 @@ pub async fn organization_retrieve() {} ), ) ), - params (("organization_id" = String, Path, description = "The unique identifier for the Organization")), + params (("id" = String, Path, description = "The unique identifier for the Organization")), responses( (status = 200, description = "Organization Created", body =OrganizationResponse), (status = 400, description = "Invalid data") @@ -104,8 +104,8 @@ pub async fn organization_create() {} /// Retrieve an existing organization #[utoipa::path( get, - path = "/v2/organization/{organization_id}", - params (("organization_id" = String, Path, description = "The unique identifier for the Organization")), + path = "/v2/organization/{id}", + params (("id" = String, Path, description = "The unique identifier for the Organization")), responses( (status = 200, description = "Organization Created", body =OrganizationResponse), (status = 400, description = "Invalid data") @@ -122,7 +122,7 @@ pub async fn organization_retrieve() {} /// Create a new organization for . #[utoipa::path( put, - path = "/v2/organization/{organization_id}", + path = "/v2/organization/{id}", request_body( content = OrganizationUpdateRequest, examples( @@ -133,7 +133,7 @@ pub async fn organization_retrieve() {} ), ) ), - params (("organization_id" = String, Path, description = "The unique identifier for the Organization")), + params (("id" = String, Path, description = "The unique identifier for the Organization")), responses( (status = 200, description = "Organization Created", body =OrganizationResponse), (status = 400, description = "Invalid data") @@ -150,8 +150,8 @@ pub async fn organization_update() {} /// List merchant accounts for an Organization #[utoipa::path( get, - path = "/v2/organization/{organization_id}/merchant_accounts", - params (("organization_id" = String, Path, description = "The unique identifier for the Organization")), + path = "/v2/organization/{id}/merchant-accounts", + params (("id" = String, Path, description = "The unique identifier for the Organization")), responses( (status = 200, description = "Merchant Account list retrieved successfully", body = Vec), (status = 400, description = "Invalid data") diff --git a/crates/openapi/src/routes/payment_method.rs b/crates/openapi/src/routes/payment_method.rs index 3bc593aa5b2b..b38a23426788 100644 --- a/crates/openapi/src/routes/payment_method.rs +++ b/crates/openapi/src/routes/payment_method.rs @@ -31,6 +31,7 @@ operation_id = "Create a Payment Method", security(("api_key" = [])) )] +#[cfg(feature = "v1")] pub async fn create_payment_method_api() {} /// List payment methods for a Merchant @@ -84,6 +85,7 @@ pub async fn list_payment_method_api() {} operation_id = "List all Payment Methods for a Customer", security(("api_key" = [])) )] +#[cfg(feature = "v1")] pub async fn list_customer_payment_method_api() {} /// List customer saved payment methods for a Payment @@ -130,6 +132,7 @@ pub async fn list_customer_payment_method_api_client() {} operation_id = "Retrieve a Payment method", security(("api_key" = [])) )] +#[cfg(feature = "v1")] pub async fn payment_method_retrieve_api() {} /// Payment Method - Update @@ -151,6 +154,7 @@ pub async fn payment_method_retrieve_api() {} operation_id = "Update a Payment method", security(("api_key" = []), ("publishable_key" = [])) )] +#[cfg(feature = "v1")] pub async fn payment_method_update_api() {} /// Payment Method - Delete @@ -170,6 +174,7 @@ pub async fn payment_method_update_api() {} operation_id = "Delete a Payment method", security(("api_key" = [])) )] +#[cfg(feature = "v1")] pub async fn payment_method_delete_api() {} /// Payment Method - Set Default Payment Method for Customer @@ -192,3 +197,171 @@ pub async fn payment_method_delete_api() {} security(("ephemeral_key" = [])) )] pub async fn default_payment_method_set_api() {} + +/// Payment Method - Create Intent +/// +/// Creates a payment method for customer with billing information and other metadata. +#[utoipa::path( + post, + path = "/v2/payment-methods/create-intent", + request_body( + content = PaymentMethodIntentCreate, + // TODO: Add examples + ), + responses( + (status = 200, description = "Payment Method Intent Created", body = PaymentMethodResponse), + (status = 400, description = "Invalid Data"), + ), + tag = "Payment Methods", + operation_id = "Create Payment Method Intent", + security(("api_key" = [])) +)] +#[cfg(feature = "v2")] +pub async fn create_payment_method_intent_api() {} + +/// Payment Method - Confirm Intent +/// +/// Update a payment method with customer's payment method related information. +#[utoipa::path( + post, + path = "/v2/payment-methods/{id}/confirm-intent", + request_body( + content = PaymentMethodIntentConfirm, + // TODO: Add examples + ), + responses( + (status = 200, description = "Payment Method Intent Confirmed", body = PaymentMethodResponse), + (status = 400, description = "Invalid Data"), + ), + tag = "Payment Methods", + operation_id = "Confirm Payment Method Intent", + security(("api_key" = [])) +)] +#[cfg(feature = "v2")] +pub async fn confirm_payment_method_intent_api() {} + +/// Payment Method - Create +/// +/// Creates and stores a payment method against a customer. In case of cards, this API should be used only by PCI compliant merchants. +#[utoipa::path( + post, + path = "/v2/payment-methods", + request_body( + content = PaymentMethodCreate, + // TODO: Add examples + ), + responses( + (status = 200, description = "Payment Method Created", body = PaymentMethodResponse), + (status = 400, description = "Invalid Data"), + ), + tag = "Payment Methods", + operation_id = "Create Payment Method", + security(("api_key" = [])) +)] +#[cfg(feature = "v2")] +pub async fn create_payment_method_api() {} + +/// Payment Method - Retrieve +/// +/// Retrieves a payment method of a customer. +#[utoipa::path( + get, + path = "/v2/payment-methods/{id}", + params ( + ("id" = String, Path, description = "The unique identifier for the Payment Method"), + ), + responses( + (status = 200, description = "Payment Method Retrieved", body = PaymentMethodResponse), + (status = 404, description = "Payment Method Not Found"), + ), + tag = "Payment Methods", + operation_id = "Retrieve Payment Method", + security(("api_key" = [])) +)] +#[cfg(feature = "v2")] +pub async fn payment_method_retrieve_api() {} + +/// Payment Method - Update +/// +/// Update an existing payment method of a customer. +#[utoipa::path( + patch, + path = "/v2/payment-methods/{id}/update-saved-payment-method", + request_body( + content = PaymentMethodUpdate, + // TODO: Add examples + ), + responses( + (status = 200, description = "Payment Method Update", body = PaymentMethodResponse), + (status = 400, description = "Invalid Data"), + ), + tag = "Payment Methods", + operation_id = "Update Payment Method", + security(("api_key" = [])) +)] +#[cfg(feature = "v2")] +pub async fn payment_method_update_api() {} + +/// Payment Method - Delete +/// +/// Deletes a payment method of a customer. +#[utoipa::path( + delete, + path = "/v2/payment-methods/{id}", + params ( + ("id" = String, Path, description = "The unique identifier for the Payment Method"), + ), + responses( + (status = 200, description = "Payment Method Retrieved", body = PaymentMethodDeleteResponse), + (status = 404, description = "Payment Method Not Found"), + ), + tag = "Payment Methods", + operation_id = "Delete Payment Method", + security(("api_key" = [])) +)] +#[cfg(feature = "v2")] +pub async fn payment_method_delete_api() {} + +/// List customer saved payment methods for a payment +/// +/// To filter and list the applicable payment methods for a particular Customer ID, is to be associated with a payment +#[utoipa::path( + get, + path = "/v2/payments/{id}/saved-payment-methods", + request_body( + content = PaymentMethodListRequest, + // TODO: Add examples and add param for customer_id + ), + responses( + (status = 200, description = "Payment Methods retrieved for customer tied to its respective client-secret passed in the param", body = CustomerPaymentMethodsListResponse), + (status = 400, description = "Invalid Data"), + (status = 404, description = "Payment Methods does not exist in records") + ), + tag = "Payment Methods", + operation_id = "List all Payment Methods for a Customer", + security(("publishable_key" = [])) +)] +#[cfg(feature = "v2")] +pub async fn list_customer_payment_method_for_payment() {} + +/// List saved payment methods for a Customer +/// +/// To filter and list the applicable payment methods for a particular Customer ID, to be used in a non-payments context +#[utoipa::path( + get, + path = "/v2/customers/{id}/saved-payment-methods", + request_body( + content = PaymentMethodListRequest, + // TODO: Add examples and add param for customer_id + ), + responses( + (status = 200, description = "Payment Methods retrieved", body = CustomerPaymentMethodsListResponse), + (status = 400, description = "Invalid Data"), + (status = 404, description = "Payment Methods does not exist in records") + ), + tag = "Payment Methods", + operation_id = "List all Payment Methods for a Customer", + security(("api_key" = [])) +)] +#[cfg(feature = "v2")] +pub async fn list_customer_payment_method_api() {} diff --git a/crates/openapi/src/routes/payments.rs b/crates/openapi/src/routes/payments.rs index 4cf8ec7cadf9..33bb95618e89 100644 --- a/crates/openapi/src/routes/payments.rs +++ b/crates/openapi/src/routes/payments.rs @@ -381,12 +381,34 @@ pub fn payments_confirm() {} )] pub fn payments_capture() {} +#[cfg(feature = "v1")] +/// Payments - Session token +/// +/// Creates a session object or a session token for wallets like Apple Pay, Google Pay, etc. These tokens are used by Hyperswitch's SDK to initiate these wallets' SDK. +#[utoipa::path( + post, + path = "/payments/session_tokens", + request_body=PaymentsSessionRequest, + responses( + (status = 200, description = "Payment session object created or session token was retrieved from wallets", body = PaymentsSessionResponse), + (status = 400, description = "Missing mandatory fields") + ), + tag = "Payments", + operation_id = "Create Session tokens for a Payment", + security(("publishable_key" = [])) +)] +pub fn payments_connector_session() {} + +#[cfg(feature = "v2")] /// Payments - Session token /// /// Creates a session object or a session token for wallets like Apple Pay, Google Pay, etc. These tokens are used by Hyperswitch's SDK to initiate these wallets' SDK. #[utoipa::path( post, - path = "/payments/session_tokens", + path = "/v2/payments/{payment_id}/create-external-sdk-tokens", + params( + ("payment_id" = String, Path, description = "The identifier for payment") + ), request_body=PaymentsSessionRequest, responses( (status = 200, description = "Payment session object created or session token was retrieved from wallets", body = PaymentsSessionResponse), @@ -527,8 +549,6 @@ pub fn payments_incremental_authorization() {} pub fn payments_external_authentication() {} /// Payments - Complete Authorize -/// -/// #[utoipa::path( post, path = "/{payment_id}/complete_authorize", @@ -547,8 +567,6 @@ pub fn payments_external_authentication() {} pub fn payments_complete_authorize() {} /// Dynamic Tax Calculation -/// -/// #[utoipa::path( post, path = "/payments/{payment_id}/calculate_tax", @@ -564,6 +582,22 @@ pub fn payments_complete_authorize() {} pub fn payments_dynamic_tax_calculation() {} +/// Payments - Post Session Tokens +#[utoipa::path( + post, + path = "/payments/{payment_id}/post_session_tokens", + request_body=PaymentsPostSessionTokensRequest, + responses( + (status = 200, description = "Post Session Token is done", body = PaymentsPostSessionTokensResponse), + (status = 400, description = "Missing mandatory fields") + ), + tag = "Payments", + operation_id = "Create Post Session Tokens for a Payment", + security(("publishable_key" = [])) +)] + +pub fn payments_post_session_tokens() {} + /// Payments - Create Intent /// /// **Creates a payment intent object when amount_details are passed.** @@ -583,7 +617,7 @@ pub fn payments_dynamic_tax_calculation() {} ), ), responses( - (status = 200, description = "Payment created", body = PaymentsCreateIntentResponse), + (status = 200, description = "Payment created", body = PaymentsIntentResponse), (status = 400, description = "Missing Mandatory fields") ), tag = "Payments", @@ -592,3 +626,171 @@ pub fn payments_dynamic_tax_calculation() {} )] #[cfg(feature = "v2")] pub fn payments_create_intent() {} + +/// Payments - Get Intent +/// +/// **Get a payment intent object when id is passed in path** +/// +/// You will require the 'API - Key' from the Hyperswitch dashboard to make the call. +#[utoipa::path( + get, + path = "/v2/payments/{id}/get-intent", + params (("id" = String, Path, description = "The unique identifier for the Payment Intent")), + responses( + (status = 200, description = "Payment Intent", body = PaymentsIntentResponse), + (status = 404, description = "Payment Intent not found") + ), + tag = "Payments", + operation_id = "Get the Payment Intent details", + security(("api_key" = [])), +)] +#[cfg(feature = "v2")] +pub fn payments_get_intent() {} + +/// Payments - Update Intent +/// +/// **Update a payment intent object** +/// +/// You will require the 'API - Key' from the Hyperswitch dashboard to make the call. +#[utoipa::path( + put, + path = "/v2/payments/{id}/update-intent", + params (("id" = String, Path, description = "The unique identifier for the Payment Intent"), + ( + "X-Profile-Id" = String, Header, + description = "Profile ID associated to the payment intent", + example = json!({"X-Profile-Id": "pro_abcdefghijklmnop"}) + ), + ), + request_body( + content = PaymentsUpdateIntentRequest, + examples( + ( + "Update a payment intent with minimal fields" = ( + value = json!({"amount_details": {"order_amount": 6540, "currency": "USD"}}) + ) + ), + ), + ), + responses( + (status = 200, description = "Payment Intent Updated", body = PaymentsIntentResponse), + (status = 404, description = "Payment Intent Not Found") + ), + tag = "Payments", + operation_id = "Update a Payment Intent", + security(("api_key" = [])), +)] +#[cfg(feature = "v2")] +pub fn payments_update_intent() {} + +/// Payments - Confirm Intent +/// +/// **Confirms a payment intent object with the payment method data** +/// +/// . +#[utoipa::path( + post, + path = "/v2/payments/{id}/confirm-intent", + params (("id" = String, Path, description = "The unique identifier for the Payment Intent"), + ( + "X-Profile-Id" = String, Header, + description = "Profile ID associated to the payment intent", + example = json!({"X-Profile-Id": "pro_abcdefghijklmnop"}) + ), + ( + "X-Client-Secret" = String, Header, + description = "Client Secret Associated with the payment intent", + example = json!({"X-Client-Secret": "12345_pay_0193e41106e07e518940f8b51b9c8121_secret_0193e41107027a928d61d292e6a5dba9"}) + ), + ), + request_body( + content = PaymentsConfirmIntentRequest, + examples( + ( + "Confirm the payment intent with card details" = ( + value = json!({ + "payment_method_type": "card", + "payment_method_data": { + "card": { + "card_number": "4242424242424242", + "card_exp_month": "10", + "card_exp_year": "25", + "card_holder_name": "joseph Doe", + "card_cvc": "123" + } + }, + }) + ) + ), + ), + ), + responses( + (status = 200, description = "Payment created", body = PaymentsConfirmIntentResponse), + (status = 400, description = "Missing Mandatory fields") + ), + tag = "Payments", + operation_id = "Confirm Payment Intent", + security(("publishable_key" = [])), +)] +#[cfg(feature = "v2")] +pub fn payments_confirm_intent() {} + +/// Payments - Get +/// +/// Retrieves a Payment. This API can also be used to get the status of a previously initiated payment or next action for an ongoing payment +#[utoipa::path( + get, + path = "/v2/payments/{id}", + params( + ("id" = String, Path, description = "The global payment id"), + ("force_sync" = ForceSync, Query, description = "A boolean to indicate whether to force sync the payment status. Value can be true or false") + ), + responses( + (status = 200, description = "Gets the payment with final status", body = PaymentsRetrieveResponse), + (status = 404, description = "No payment found with the given id") + ), + tag = "Payments", + operation_id = "Retrieve a Payment", + security(("api_key" = [])) +)] +#[cfg(feature = "v2")] +pub fn payment_status() {} + +#[derive(utoipa::ToSchema)] +#[schema(rename_all = "lowercase")] +pub(crate) enum ForceSync { + /// Force sync with the connector / processor to update the status + True, + /// Do not force sync with the connector / processor. Get the status which is available in the database + False, +} + +/// Payments - Payment Methods List +/// +/// List the payment methods eligible for a payment. This endpoint also returns the saved payment methods for the customer when the customer_id is passed when creating the payment +#[cfg(feature = "v2")] +#[utoipa::path( + get, + path = "/v2/payments/{id}/payment-methods", + params( + ("id" = String, Path, description = "The global payment id"), + ( + "X-Profile-Id" = String, Header, + description = "Profile ID associated to the payment intent", + example = json!({"X-Profile-Id": "pro_abcdefghijklmnop"}) + ), + ( + "X-Client-Secret" = String, Header, + description = "Client Secret Associated with the payment intent", + example = json!({"X-Client-Secret": "12345_pay_0193e41106e07e518940f8b51b9c8121_secret_0193e41107027a928d61d292e6a5dba9"}) + ), + ), + responses( + (status = 200, description = "Get the payment methods", body = PaymentMethodListResponseForPayments), + (status = 404, description = "No payment found with the given id") + ), + tag = "Payments", + operation_id = "Retrieve Payment methods for a Payment", + security(("publishable_key" = [])) +)] +pub fn list_payment_methods() {} diff --git a/crates/openapi/src/routes/profile.rs b/crates/openapi/src/routes/profile.rs index f726c76db84b..cc484aa3f953 100644 --- a/crates/openapi/src/routes/profile.rs +++ b/crates/openapi/src/routes/profile.rs @@ -139,6 +139,13 @@ pub async fn profile_list() {} #[utoipa::path( post, path = "/v2/profiles", + params( + ( + "X-Merchant-Id" = String, Header, + description = "Merchant ID of the profile.", + example = json!({"X-Merchant-Id": "abc_iG5VNjsN9xuCg7Xx0uWh"}) + ), + ), request_body( content = ProfileCreate, examples( @@ -167,9 +174,14 @@ pub async fn profile_create() {} /// Update the *profile* #[utoipa::path( put, - path = "/v2/profiles/{profile_id}", + path = "/v2/profiles/{id}", params( - ("profile_id" = String, Path, description = "The unique identifier for the profile") + ("id" = String, Path, description = "The unique identifier for the profile"), + ( + "X-Merchant-Id" = String, Header, + description = "Merchant ID of the profile.", + example = json!({"X-Merchant-Id": "abc_iG5VNjsN9xuCg7Xx0uWh"}) + ), ), request_body( content = ProfileCreate, @@ -198,7 +210,7 @@ pub async fn profile_update() {} /// Activates a routing algorithm under a profile #[utoipa::path( patch, - path = "/v2/profiles/{profile_id}/activate_routing_algorithm", + path = "/v2/profiles/{id}/activate-routing-algorithm", request_body ( content = RoutingAlgorithmId, examples( ( "Activate a routing algorithm" = ( @@ -208,7 +220,7 @@ pub async fn profile_update() {} ) ))), params( - ("profile_id" = String, Path, description = "The unique identifier for the profile"), + ("id" = String, Path, description = "The unique identifier for the profile"), ), responses( (status = 200, description = "Routing Algorithm is activated", body = RoutingDictionaryRecord), @@ -228,9 +240,9 @@ pub async fn routing_link_config() {} /// Deactivates a routing algorithm under a profile #[utoipa::path( patch, - path = "/v2/profiles/{profile_id}/deactivate_routing_algorithm", + path = "/v2/profiles/{id}/deactivate-routing-algorithm", params( - ("profile_id" = String, Path, description = "The unique identifier for the profile"), + ("id" = String, Path, description = "The unique identifier for the profile"), ), responses( (status = 200, description = "Successfully deactivated routing config", body = RoutingDictionaryRecord), @@ -251,10 +263,10 @@ pub async fn routing_unlink_config() {} /// Update the default fallback routing algorithm for the profile #[utoipa::path( patch, - path = "/v2/profiles/{profile_id}/fallback_routing", + path = "/v2/profiles/{id}/fallback-routing", request_body = Vec, params( - ("profile_id" = String, Path, description = "The unique identifier for the profile"), + ("id" = String, Path, description = "The unique identifier for the profile"), ), responses( (status = 200, description = "Successfully updated the default fallback routing algorithm", body = Vec), @@ -274,9 +286,14 @@ pub async fn routing_update_default_config() {} /// Retrieve existing *profile* #[utoipa::path( get, - path = "/v2/profiles/{profile_id}", + path = "/v2/profiles/{id}", params( - ("profile_id" = String, Path, description = "The unique identifier for the profile") + ("id" = String, Path, description = "The unique identifier for the profile"), + ( + "X-Merchant-Id" = String, Header, + description = "Merchant ID of the profile.", + example = json!({"X-Merchant-Id": "abc_iG5VNjsN9xuCg7Xx0uWh"}) + ), ), responses( (status = 200, description = "Profile Updated", body = ProfileResponse), @@ -290,13 +307,13 @@ pub async fn profile_retrieve() {} #[cfg(feature = "v2")] /// Profile - Retrieve Active Routing Algorithm -/// +///_ /// Retrieve active routing algorithm under the profile #[utoipa::path( get, - path = "/v2/profiles/{profile_id}/routing_algorithm", + path = "/v2/profiles/{id}/routing-algorithm", params( - ("profile_id" = String, Path, description = "The unique identifier for the profile"), + ("id" = String, Path, description = "The unique identifier for the profile"), ("limit" = Option, Query, description = "The number of records of the algorithms to be returned"), ("offset" = Option, Query, description = "The record offset of the algorithm from which to start gathering the results")), responses( @@ -317,9 +334,9 @@ pub async fn routing_retrieve_linked_config() {} /// Retrieve the default fallback routing algorithm for the profile #[utoipa::path( get, - path = "/v2/profiles/{profile_id}/fallback_routing", + path = "/v2/profiles/{id}/fallback-routing", params( - ("profile_id" = String, Path, description = "The unique identifier for the profile"), + ("id" = String, Path, description = "The unique identifier for the profile"), ), responses( (status = 200, description = "Successfully retrieved default fallback routing algorithm", body = Vec), @@ -331,14 +348,19 @@ pub async fn routing_retrieve_linked_config() {} )] pub async fn routing_retrieve_default_config() {} -/// Merchant Connector - List +/// Profile - Connector Accounts List /// -/// List Merchant Connector Details for the business profile +/// List Connector Accounts for the profile #[utoipa::path( get, - path = "/v2/profiles/{profile_id}/connector_accounts", + path = "/v2/profiles/{id}/connector-accounts", params( - ("profile_id" = String, Path, description = "The unique identifier for the business profile"), + ("id" = String, Path, description = "The unique identifier for the business profile"), + ( + "X-Merchant-Id" = String, Header, + description = "Merchant ID of the profile.", + example = json!({"X-Merchant-Id": "abc_iG5VNjsN9xuCg7Xx0uWh"}) + ), ), responses( (status = 200, description = "Merchant Connector list retrieved successfully", body = Vec), diff --git a/crates/openapi/src/routes/refunds.rs b/crates/openapi/src/routes/refunds.rs index 5c72fe8b48fe..0ff0891ee54b 100644 --- a/crates/openapi/src/routes/refunds.rs +++ b/crates/openapi/src/routes/refunds.rs @@ -44,6 +44,7 @@ operation_id = "Create a Refund", security(("api_key" = [])) )] +#[cfg(feature = "v1")] pub async fn refunds_create() {} /// Refunds - Retrieve @@ -114,7 +115,7 @@ pub async fn refunds_update() {} /// Refunds - List /// -/// Lists all the refunds associated with the merchant or a payment_id if payment_id is not provided +/// Lists all the refunds associated with the merchant, or for a specific payment if payment_id is provided #[utoipa::path( post, path = "/refunds/list", @@ -159,3 +160,55 @@ pub fn refunds_list_profile() {} security(("api_key" = [])) )] pub async fn refunds_filter_list() {} + +/// Refunds - Create +/// +/// Creates a refund against an already processed payment. In case of some processors, you can even opt to refund only a partial amount multiple times until the original charge amount has been refunded +#[utoipa::path( + post, + path = "/v2/refunds", + request_body( + content = RefundsCreateRequest, + examples( + ( + "Create an instant refund to refund the whole amount" = ( + value = json!({ + "payment_id": "{{payment_id}}", + "merchant_reference_id": "ref_123", + "refund_type": "instant" + }) + ) + ), + ( + "Create an instant refund to refund partial amount" = ( + value = json!({ + "payment_id": "{{payment_id}}", + "merchant_reference_id": "ref_123", + "refund_type": "instant", + "amount": 654 + }) + ) + ), + ( + "Create an instant refund with reason" = ( + value = json!({ + "payment_id": "{{payment_id}}", + "merchant_reference_id": "ref_123", + "refund_type": "instant", + "amount": 6540, + "reason": "Customer returned product" + }) + ) + ), + ) + ), + responses( + (status = 200, description = "Refund created", body = RefundResponse), + (status = 400, description = "Missing Mandatory fields") + ), + tag = "Refunds", + operation_id = "Create a Refund", + security(("api_key" = [])) +)] +#[cfg(feature = "v2")] +pub async fn refunds_create() {} diff --git a/crates/openapi/src/routes/relay.rs b/crates/openapi/src/routes/relay.rs new file mode 100644 index 000000000000..180fed664924 --- /dev/null +++ b/crates/openapi/src/routes/relay.rs @@ -0,0 +1,59 @@ +/// Relay - Create +/// +/// Creates a relay request. +#[utoipa::path( + post, + path = "/relay", + request_body( + content = RelayRequest, + examples(( + "Create a relay request" = ( + value = json!({ + "connector_resource_id": "7256228702616471803954", + "connector_id": "mca_5apGeP94tMts6rg3U3kR", + "type": "refund", + "data": { + "refund": { + "amount": 6540, + "currency": "USD" + } + } + }) + ) + )) + ), + responses( + (status = 200, description = "Relay request", body = RelayResponse), + (status = 400, description = "Invalid data") + ), + params( + ("X-Profile-Id" = String, Header, description = "Profile ID for authentication"), + ("X-Idempotency-Key" = String, Header, description = "Idempotency Key for relay request") + ), + tag = "Relay", + operation_id = "Relay Request", + security(("api_key" = [])) +)] + +pub async fn relay() {} + +/// Relay - Retrieve +/// +/// Retrieves a relay details. +#[utoipa::path( + get, + path = "/relay/{relay_id}", + params (("id" = String, Path, description = "The unique identifier for the Relay")), + responses( + (status = 200, description = "Relay Retrieved", body = RelayResponse), + (status = 404, description = "Relay details was not found") + ), + params( + ("X-Profile-Id" = String, Header, description = "Profile ID for authentication") + ), + tag = "Relay", + operation_id = "Retrieve a Relay details", + security(("api_key" = []), ("ephemeral_key" = [])) +)] + +pub async fn relay_retrieve() {} diff --git a/crates/openapi/src/routes/routing.rs b/crates/openapi/src/routes/routing.rs index 009708d860d0..6b968f80172e 100644 --- a/crates/openapi/src/routes/routing.rs +++ b/crates/openapi/src/routes/routing.rs @@ -26,7 +26,7 @@ pub async fn routing_create_config() {} /// Create a routing algorithm #[utoipa::path( post, - path = "/v2/routing_algorithm", + path = "/v2/routing-algorithm", request_body = RoutingConfigRequest, responses( (status = 200, description = "Routing Algorithm created", body = RoutingDictionaryRecord), @@ -37,7 +37,7 @@ pub async fn routing_create_config() {} (status = 403, description = "Forbidden"), ), tag = "Routing", - operation_id = "Create a routing algprithm", + operation_id = "Create a routing algorithm", security(("api_key" = []), ("jwt_key" = [])) )] pub async fn routing_create_config() {} @@ -68,7 +68,6 @@ pub async fn routing_link_config() {} /// Routing - Retrieve /// /// Retrieve a routing algorithm - #[utoipa::path( get, path = "/routing/{routing_algorithm_id}", @@ -91,12 +90,11 @@ pub async fn routing_retrieve_config() {} /// Routing - Retrieve /// /// Retrieve a routing algorithm with its algorithm id - #[utoipa::path( get, - path = "/v2/routing_algorithm/{routing_algorithm_id}", + path = "/v2/routing-algorithm/{id}", params( - ("routing_algorithm_id" = String, Path, description = "The unique identifier for a routing algorithm"), + ("id" = String, Path, description = "The unique identifier for a routing algorithm"), ), responses( (status = 200, description = "Successfully fetched routing algorithm", body = MerchantRoutingAlgorithm), @@ -262,11 +260,11 @@ pub async fn routing_update_default_config_for_profile() {} /// Create a success based dynamic routing algorithm #[utoipa::path( post, - path = "/account/:account_id/business_profile/:profile_id/dynamic_routing/success_based/toggle", + path = "/account/{account_id}/business_profile/{profile_id}/dynamic_routing/success_based/toggle", params( ("account_id" = String, Path, description = "Merchant id"), ("profile_id" = String, Path, description = "Profile id under which Dynamic routing needs to be toggled"), - ("status" = bool, Query, description = "Boolean value for mentioning the expected state of dynamic routing"), + ("enable" = DynamicRoutingFeatures, Query, description = "Feature to enable for success based routing"), ), responses( (status = 200, description = "Routing Algorithm created", body = RoutingDictionaryRecord), @@ -277,34 +275,60 @@ pub async fn routing_update_default_config_for_profile() {} (status = 403, description = "Forbidden"), ), tag = "Routing", - operation_id = "Toggle success based dynamic routing algprithm", + operation_id = "Toggle success based dynamic routing algorithm", security(("api_key" = []), ("jwt_key" = [])) )] pub async fn toggle_success_based_routing() {} #[cfg(feature = "v1")] -/// Routing - Update config for success based dynamic routing +/// Routing - Update success based dynamic routing config for profile /// -/// Update config for success based dynamic routing +/// Update success based dynamic routing algorithm #[utoipa::path( patch, - path = "/account/:account_id/business_profile/:profile_id/dynamic_routing/success_based/config/:algorithm_id", - request_body = SuccessBasedRoutingConfig, + path = "/account/{account_id}/business_profile/{profile_id}/dynamic_routing/success_based/config/{algorithm_id}", params( ("account_id" = String, Path, description = "Merchant id"), - ("profile_id" = String, Path, description = "The unique identifier for a profile"), - ("algorithm_id" = String, Path, description = "The unique identifier for routing algorithm"), + ("profile_id" = String, Path, description = "Profile id under which Dynamic routing needs to be toggled"), + ("algorithm_id" = String, Path, description = "Success based routing algorithm id which was last activated to update the config"), ), + request_body = DynamicRoutingFeatures, responses( (status = 200, description = "Routing Algorithm updated", body = RoutingDictionaryRecord), - (status = 400, description = "Request body is malformed"), + (status = 400, description = "Update body is malformed"), (status = 500, description = "Internal server error"), (status = 404, description = "Resource missing"), (status = 422, description = "Unprocessable request"), (status = 403, description = "Forbidden"), ), tag = "Routing", - operation_id = "Update configs for success based dynamic routing algorithm", - security(("admin_api_key" = [])) + operation_id = "Update success based dynamic routing configs", + security(("api_key" = []), ("jwt_key" = [])) )] pub async fn success_based_routing_update_configs() {} + +#[cfg(feature = "v1")] +/// Routing - Toggle elimination routing for profile +/// +/// Create a elimination based dynamic routing algorithm +#[utoipa::path( + post, + path = "/account/{account_id}/business_profile/{profile_id}/dynamic_routing/elimination/toggle", + params( + ("account_id" = String, Path, description = "Merchant id"), + ("profile_id" = String, Path, description = "Profile id under which Dynamic routing needs to be toggled"), + ("enable" = DynamicRoutingFeatures, Query, description = "Feature to enable for success based routing"), + ), + responses( + (status = 200, description = "Routing Algorithm created", body = RoutingDictionaryRecord), + (status = 400, description = "Request body is malformed"), + (status = 500, description = "Internal server error"), + (status = 404, description = "Resource missing"), + (status = 422, description = "Unprocessable request"), + (status = 403, description = "Forbidden"), + ), + tag = "Routing", + operation_id = "Toggle elimination routing algorithm", + security(("api_key" = []), ("jwt_key" = [])) +)] +pub async fn toggle_elimination_routing() {} diff --git a/crates/pm_auth/Cargo.toml b/crates/pm_auth/Cargo.toml index df8136ba8332..3923d44b7fad 100644 --- a/crates/pm_auth/Cargo.toml +++ b/crates/pm_auth/Cargo.toml @@ -8,7 +8,7 @@ readme = "README.md" license.workspace = true [features] -v1 = ["api_models/v1"] +v1 = ["api_models/v1", "common_utils/v1"] [dependencies] # First party crates diff --git a/crates/pm_auth/src/connector/plaid/transformers.rs b/crates/pm_auth/src/connector/plaid/transformers.rs index af0c26a5b858..a10ff0d60e5c 100644 --- a/crates/pm_auth/src/connector/plaid/transformers.rs +++ b/crates/pm_auth/src/connector/plaid/transformers.rs @@ -20,7 +20,6 @@ pub struct PlaidLinkTokenRequest { } #[derive(Debug, Serialize, Eq, PartialEq)] - pub struct User { pub client_user_id: id_type::CustomerId, } @@ -45,28 +44,20 @@ impl TryFrom<&types::LinkTokenRouterData> for PlaidLinkTokenRequest { )?, }, android_package_name: match item.request.client_platform { - api_models::enums::ClientPlatform::Android => { - Some(item.request.android_package_name.clone().ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "android_package_name", - }, - )?) + Some(api_models::enums::ClientPlatform::Android) => { + item.request.android_package_name.clone() } - api_models::enums::ClientPlatform::Ios - | api_models::enums::ClientPlatform::Web - | api_models::enums::ClientPlatform::Unknown => None, + Some(api_models::enums::ClientPlatform::Ios) + | Some(api_models::enums::ClientPlatform::Web) + | Some(api_models::enums::ClientPlatform::Unknown) + | None => None, }, redirect_uri: match item.request.client_platform { - api_models::enums::ClientPlatform::Ios => { - Some(item.request.redirect_uri.clone().ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "redirect_uri", - }, - )?) - } - api_models::enums::ClientPlatform::Android - | api_models::enums::ClientPlatform::Web - | api_models::enums::ClientPlatform::Unknown => None, + Some(api_models::enums::ClientPlatform::Ios) => item.request.redirect_uri.clone(), + Some(api_models::enums::ClientPlatform::Android) + | Some(api_models::enums::ClientPlatform::Web) + | Some(api_models::enums::ClientPlatform::Unknown) + | None => None, }, }) } @@ -102,7 +93,6 @@ pub struct PlaidExchangeTokenRequest { } #[derive(Debug, Deserialize, Eq, PartialEq)] - pub struct PlaidExchangeTokenResponse { pub access_token: String, } @@ -244,7 +234,6 @@ pub struct PlaidBankAccountCredentialsRequest { } #[derive(Debug, Deserialize, PartialEq)] - pub struct PlaidBankAccountCredentialsResponse { pub accounts: Vec, pub numbers: PlaidBankAccountCredentialsNumbers, @@ -259,7 +248,6 @@ pub struct BankAccountCredentialsOptions { } #[derive(Debug, Deserialize, PartialEq)] - pub struct PlaidBankAccountCredentialsAccounts { pub account_id: String, pub name: String, diff --git a/crates/pm_auth/src/types.rs b/crates/pm_auth/src/types.rs index 878043afe508..bae9ee59848f 100644 --- a/crates/pm_auth/src/types.rs +++ b/crates/pm_auth/src/types.rs @@ -25,7 +25,7 @@ pub struct LinkTokenRequest { pub country_codes: Option>, pub language: Option, pub user_info: Option, - pub client_platform: api_enums::ClientPlatform, + pub client_platform: Option, pub android_package_name: Option, pub redirect_uri: Option, } diff --git a/crates/redis_interface/src/commands.rs b/crates/redis_interface/src/commands.rs index 9fd07a473d4a..19497d6fbb83 100644 --- a/crates/redis_interface/src/commands.rs +++ b/crates/redis_interface/src/commands.rs @@ -3,8 +3,6 @@ //! The folder provides generic functions for providing serialization //! and deserialization while calling redis. //! It also includes instruments to provide tracing. -//! -//! use std::fmt::Debug; @@ -16,7 +14,7 @@ use common_utils::{ use error_stack::{report, ResultExt}; use fred::{ interfaces::{HashesInterface, KeysInterface, ListInterface, SetsInterface, StreamsInterface}, - prelude::RedisErrorKind, + prelude::{LuaInterface, RedisErrorKind}, types::{ Expiration, FromRedis, MultipleIDs, MultipleKeys, MultipleOrderedPairs, MultipleStrings, MultipleValues, RedisKey, RedisMap, RedisValue, ScanType, Scanner, SetOptions, XCap, @@ -854,12 +852,31 @@ impl super::RedisConnectionPool { .await .change_context(errors::RedisError::ConsumerGroupClaimFailed) } + + #[instrument(level = "DEBUG", skip(self))] + pub async fn incr_keys_using_script( + &self, + lua_script: &'static str, + key: Vec, + values: V, + ) -> CustomResult<(), errors::RedisError> + where + V: TryInto + Debug + Send + Sync, + V::Error: Into + Send + Sync, + { + self.pool + .eval(lua_script, key, values) + .await + .change_context(errors::RedisError::IncrementHashFieldFailed) + } } #[cfg(test)] mod tests { #![allow(clippy::expect_used, clippy::unwrap_used)] + use std::collections::HashMap; + use crate::{errors::RedisError, RedisConnectionPool, RedisEntryId, RedisSettings}; #[tokio::test] @@ -913,7 +930,6 @@ mod tests { assert!(is_success); } - #[tokio::test] async fn test_delete_non_existing_key_success() { let is_success = tokio::task::spawn_blocking(move || { @@ -932,6 +948,44 @@ mod tests { }) .await .expect("Spawn block failure"); + assert!(is_success); + } + + #[tokio::test] + async fn test_setting_keys_using_scripts() { + let is_success = tokio::task::spawn_blocking(move || { + futures::executor::block_on(async { + // Arrange + let pool = RedisConnectionPool::new(&RedisSettings::default()) + .await + .expect("failed to create redis connection pool"); + let lua_script = r#" + local results = {} + for i = 1, #KEYS do + results[i] = redis.call("INCRBY", KEYS[i], ARGV[i]) + end + return results + "#; + let mut keys_and_values = HashMap::new(); + for i in 0..10 { + keys_and_values.insert(format!("key{}", i), i); + } + + let key = keys_and_values.keys().cloned().collect::>(); + let values = keys_and_values + .values() + .map(|val| val.to_string()) + .collect::>(); + + // Act + let result = pool.incr_keys_using_script(lua_script, key, values).await; + + // Assert Setup + result.is_ok() + }) + }) + .await + .expect("Spawn block failure"); assert!(is_success); } diff --git a/crates/redis_interface/src/errors.rs b/crates/redis_interface/src/errors.rs index 0e2a4b8d63b0..9e0fb4639a5d 100644 --- a/crates/redis_interface/src/errors.rs +++ b/crates/redis_interface/src/errors.rs @@ -1,6 +1,4 @@ -//! //! Errors specific to this custom redis interface -//! #[derive(Debug, thiserror::Error, PartialEq)] pub enum RedisError { diff --git a/crates/redis_interface/src/types.rs b/crates/redis_interface/src/types.rs index f40b81af68e2..92429f617d13 100644 --- a/crates/redis_interface/src/types.rs +++ b/crates/redis_interface/src/types.rs @@ -1,7 +1,5 @@ -//! //! Data types and type conversions //! from `fred`'s internal data-types to custom data-types -//! use common_utils::errors::CustomResult; use fred::types::RedisValue as FredRedisValue; diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index 31cfae498750..619d2fb19375 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -17,7 +17,7 @@ email = ["external_services/email", "scheduler/email", "olap"] # keymanager_create, keymanager_mtls, encryption_service should not be removed or added to default feature. Once this features were enabled it can't be disabled as these are breaking changes. keymanager_create = [] keymanager_mtls = ["reqwest/rustls-tls", "common_utils/keymanager_mtls"] -encryption_service = ["hyperswitch_domain_models/encryption_service", "common_utils/encryption_service"] +encryption_service = ["keymanager_create","hyperswitch_domain_models/encryption_service", "common_utils/encryption_service"] km_forward_x_request_id = ["common_utils/km_forward_x_request_id"] frm = ["api_models/frm", "hyperswitch_domain_models/frm", "hyperswitch_connectors/frm", "hyperswitch_interfaces/frm"] stripe = [] @@ -34,10 +34,10 @@ payout_retry = ["payouts"] recon = ["email", "api_models/recon"] retry = [] v2 = ["customer_v2", "payment_methods_v2", "common_default", "api_models/v2", "diesel_models/v2", "hyperswitch_domain_models/v2", "storage_impl/v2", "kgraph_utils/v2", "common_utils/v2"] -v1 = ["common_default", "api_models/v1", "diesel_models/v1", "hyperswitch_domain_models/v1", "storage_impl/v1", "hyperswitch_interfaces/v1", "kgraph_utils/v1"] +v1 = ["common_default", "api_models/v1", "diesel_models/v1", "hyperswitch_domain_models/v1", "storage_impl/v1", "hyperswitch_interfaces/v1", "kgraph_utils/v1", "common_utils/v1"] customer_v2 = ["api_models/customer_v2", "diesel_models/customer_v2", "hyperswitch_domain_models/customer_v2", "storage_impl/customer_v2"] payment_methods_v2 = ["api_models/payment_methods_v2", "diesel_models/payment_methods_v2", "hyperswitch_domain_models/payment_methods_v2", "storage_impl/payment_methods_v2", "common_utils/payment_methods_v2"] -dynamic_routing = ["external_services/dynamic_routing", "storage_impl/dynamic_routing"] +dynamic_routing = ["external_services/dynamic_routing", "storage_impl/dynamic_routing", "api_models/dynamic_routing"] # Partial Auth # The feature reduces the overhead of the router authenticating the merchant for every request, and trusts on `x-merchant-id` header to be present in the request. @@ -91,7 +91,6 @@ rdkafka = "0.36.2" regex = "1.10.4" reqwest = { version = "0.11.27", features = ["json", "rustls-tls", "gzip", "multipart"] } ring = "0.17.8" -roxmltree = "0.19.0" rust_decimal = { version = "1.35.0", features = ["serde-with-float", "serde-with-str"] } rust-i18n = { git = "https://github.com/kashif-m/rust-i18n", rev = "f2d8096aaaff7a87a847c35a5394c269f75e077a" } rustc-hash = "1.1.0" @@ -115,6 +114,7 @@ tracing-futures = { version = "0.2.5", features = ["tokio"] } unicode-segmentation = "1.11.0" unidecode = "0.3.0" url = { version = "2.5.0", features = ["serde"] } +urlencoding = "2.1.3" utoipa = { version = "4.2.0", features = ["preserve_order", "preserve_path_order", "time"] } uuid = { version = "1.8.0", features = ["v4"] } validator = "0.17.0" @@ -123,10 +123,11 @@ x509-parser = "0.16.0" # First party crates analytics = { version = "0.1.0", path = "../analytics", optional = true, default-features = false } -api_models = { version = "0.1.0", path = "../api_models", features = ["errors"] } +api_models = { version = "0.1.0", path = "../api_models", features = ["errors", "control_center_theme"] } cards = { version = "0.1.0", path = "../cards" } common_enums = { version = "0.1.0", path = "../common_enums" } common_utils = { version = "0.1.0", path = "../common_utils", features = ["signals", "async_ext", "logs", "metrics", "keymanager", "encryption_service"] } +common_types = { version = "0.1.0", path = "../common_types" } currency_conversion = { version = "0.1.0", path = "../currency_conversion" } diesel_models = { version = "0.1.0", path = "../diesel_models", features = ["kv_store"], default-features = false } euclid = { version = "0.1.0", path = "../euclid", features = ["valued_jit"] } diff --git a/crates/router/build.rs b/crates/router/build.rs index b33c168833d2..7c8043c48ade 100644 --- a/crates/router/build.rs +++ b/crates/router/build.rs @@ -1,8 +1,8 @@ fn main() { - // Set thread stack size to 8 MiB for debug builds + // Set thread stack size to 10 MiB for debug builds // Reference: https://doc.rust-lang.org/std/thread/#stack-size #[cfg(debug_assertions)] - println!("cargo:rustc-env=RUST_MIN_STACK=8388608"); // 8 * 1024 * 1024 = 8 MiB + println!("cargo:rustc-env=RUST_MIN_STACK=10485760"); // 10 * 1024 * 1024 = 10 MiB #[cfg(feature = "vergen")] router_env::vergen::generate_cargo_instructions(); diff --git a/crates/router/src/analytics.rs b/crates/router/src/analytics.rs index a0b6606c2276..8fe30aa59d4a 100644 --- a/crates/router/src/analytics.rs +++ b/crates/router/src/analytics.rs @@ -1,6 +1,11 @@ pub use analytics::*; pub mod routes { + use std::{ + collections::{HashMap, HashSet}, + sync::Arc, + }; + use actix_web::{web, Responder, Scope}; use analytics::{ api_event::api_events_core, connector_events::connector_events_core, enums::AuthInfo, @@ -13,21 +18,23 @@ pub mod routes { search::{ GetGlobalSearchRequest, GetSearchRequest, GetSearchRequestWithIndex, SearchIndex, }, - GenerateReportRequest, GetActivePaymentsMetricRequest, GetApiEventFiltersRequest, - GetApiEventMetricRequest, GetAuthEventMetricRequest, GetDisputeMetricRequest, - GetFrmFilterRequest, GetFrmMetricRequest, GetPaymentFiltersRequest, - GetPaymentIntentFiltersRequest, GetPaymentIntentMetricRequest, GetPaymentMetricRequest, - GetRefundFilterRequest, GetRefundMetricRequest, GetSdkEventFiltersRequest, - GetSdkEventMetricRequest, ReportRequest, + AnalyticsRequest, GenerateReportRequest, GetActivePaymentsMetricRequest, + GetApiEventFiltersRequest, GetApiEventMetricRequest, GetAuthEventMetricRequest, + GetDisputeMetricRequest, GetFrmFilterRequest, GetFrmMetricRequest, + GetPaymentFiltersRequest, GetPaymentIntentFiltersRequest, GetPaymentIntentMetricRequest, + GetPaymentMetricRequest, GetRefundFilterRequest, GetRefundMetricRequest, + GetSdkEventFiltersRequest, GetSdkEventMetricRequest, ReportRequest, }; use common_enums::EntityType; - use common_utils::id_type::{MerchantId, OrganizationId}; + use common_utils::types::TimeRange; use error_stack::{report, ResultExt}; + use futures::{stream::FuturesUnordered, StreamExt}; use crate::{ - consts::opensearch::OPENSEARCH_INDEX_PERMISSIONS, + analytics_validator::request_validator, + consts::opensearch::SEARCH_INDEXES, core::{api_locking, errors::user::UserErrors, verification::utils}, - db::user::UserInterface, + db::{user::UserInterface, user_role::ListUserRolesByUserIdPayload}, routes::AppState, services::{ api, @@ -35,12 +42,17 @@ pub mod routes { authorization::{permissions::Permission, roles::RoleInfo}, ApplicationResponse, }, - types::domain::UserEmail, + types::{domain::UserEmail, storage::UserRole}, }; pub struct Analytics; impl Analytics { + #[cfg(feature = "v2")] + pub fn server(state: AppState) -> Scope { + web::scope("/analytics").app_data(web::Data::new(state)) + } + #[cfg(feature = "v1")] pub fn server(state: AppState) -> Scope { web::scope("/analytics") .app_data(web::Data::new(state)) @@ -137,6 +149,10 @@ pub mod routes { web::resource("filters/disputes") .route(web::post().to(get_merchant_dispute_filters)), ) + .service( + web::resource("metrics/sankey") + .route(web::post().to(get_merchant_sankey)), + ) .service( web::scope("/merchant") .service( @@ -185,6 +201,10 @@ pub mod routes { .service( web::resource("filters/disputes") .route(web::post().to(get_merchant_dispute_filters)), + ) + .service( + web::resource("metrics/sankey") + .route(web::post().to(get_merchant_sankey)), ), ) .service( @@ -227,6 +247,10 @@ pub mod routes { .service( web::resource("report/payments") .route(web::post().to(generate_org_payment_report)), + ) + .service( + web::resource("metrics/sankey") + .route(web::post().to(get_org_sankey)), ), ) .service( @@ -285,6 +309,10 @@ pub mod routes { .service( web::resource("sdk_event_logs") .route(web::post().to(get_profile_sdk_events)), + ) + .service( + web::resource("metrics/sankey") + .route(web::post().to(get_profile_sankey)), ), ), ) @@ -375,13 +403,21 @@ pub mod routes { org_id: org_id.clone(), merchant_ids: vec![merchant_id.clone()], }; - analytics::payments::get_metrics(&state.pool, &auth, req) + let validator_response = request_validator( + AnalyticsRequest { + payment_attempt: Some(req.clone()), + ..Default::default() + }, + &state, + ) + .await?; + let ex_rates = validator_response; + analytics::payments::get_metrics(&state.pool, &ex_rates, &auth, req) .await .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Merchant, + permission: Permission::MerchantAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) @@ -414,19 +450,29 @@ pub mod routes { let auth: AuthInfo = AuthInfo::OrgLevel { org_id: org_id.clone(), }; - analytics::payments::get_metrics(&state.pool, &auth, req) + + let validator_response = request_validator( + AnalyticsRequest { + payment_attempt: Some(req.clone()), + ..Default::default() + }, + &state, + ) + .await?; + let ex_rates = validator_response; + analytics::payments::get_metrics(&state.pool, &ex_rates, &auth, req) .await .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Organization, + permission: Permission::OrganizationAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) .await } + #[cfg(feature = "v1")] /// # Panics /// /// Panics if `json_payload` array does not contain one `GetPaymentMetricRequest` element. @@ -460,13 +506,22 @@ pub mod routes { merchant_id: merchant_id.clone(), profile_ids: vec![profile_id.clone()], }; - analytics::payments::get_metrics(&state.pool, &auth, req) + + let validator_response = request_validator( + AnalyticsRequest { + payment_attempt: Some(req.clone()), + ..Default::default() + }, + &state, + ) + .await?; + let ex_rates = validator_response; + analytics::payments::get_metrics(&state.pool, &ex_rates, &auth, req) .await .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Profile, + permission: Permission::ProfileAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) @@ -501,13 +556,22 @@ pub mod routes { org_id: org_id.clone(), merchant_ids: vec![merchant_id.clone()], }; - analytics::payment_intents::get_metrics(&state.pool, &auth, req) + + let validator_response = request_validator( + AnalyticsRequest { + payment_intent: Some(req.clone()), + ..Default::default() + }, + &state, + ) + .await?; + let ex_rates = validator_response; + analytics::payment_intents::get_metrics(&state.pool, &ex_rates, &auth, req) .await .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Merchant, + permission: Permission::MerchantAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) @@ -540,19 +604,29 @@ pub mod routes { let auth: AuthInfo = AuthInfo::OrgLevel { org_id: org_id.clone(), }; - analytics::payment_intents::get_metrics(&state.pool, &auth, req) + + let validator_response = request_validator( + AnalyticsRequest { + payment_intent: Some(req.clone()), + ..Default::default() + }, + &state, + ) + .await?; + let ex_rates = validator_response; + analytics::payment_intents::get_metrics(&state.pool, &ex_rates, &auth, req) .await .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Organization, + permission: Permission::OrganizationAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) .await } + #[cfg(feature = "v1")] /// # Panics /// /// Panics if `json_payload` array does not contain one `GetPaymentIntentMetricRequest` element. @@ -586,13 +660,22 @@ pub mod routes { merchant_id: merchant_id.clone(), profile_ids: vec![profile_id.clone()], }; - analytics::payment_intents::get_metrics(&state.pool, &auth, req) + + let validator_response = request_validator( + AnalyticsRequest { + payment_intent: Some(req.clone()), + ..Default::default() + }, + &state, + ) + .await?; + let ex_rates = validator_response; + analytics::payment_intents::get_metrics(&state.pool, &ex_rates, &auth, req) .await .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Profile, + permission: Permission::ProfileAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) @@ -627,13 +710,22 @@ pub mod routes { org_id: org_id.clone(), merchant_ids: vec![merchant_id.clone()], }; - analytics::refunds::get_metrics(&state.pool, &auth, req) + + let validator_response = request_validator( + AnalyticsRequest { + refund: Some(req.clone()), + ..Default::default() + }, + &state, + ) + .await?; + let ex_rates = validator_response; + analytics::refunds::get_metrics(&state.pool, &ex_rates, &auth, req) .await .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Merchant, + permission: Permission::MerchantAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) @@ -666,19 +758,29 @@ pub mod routes { let auth: AuthInfo = AuthInfo::OrgLevel { org_id: org_id.clone(), }; - analytics::refunds::get_metrics(&state.pool, &auth, req) + + let validator_response = request_validator( + AnalyticsRequest { + refund: Some(req.clone()), + ..Default::default() + }, + &state, + ) + .await?; + let ex_rates = validator_response; + analytics::refunds::get_metrics(&state.pool, &ex_rates, &auth, req) .await .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Organization, + permission: Permission::OrganizationAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) .await } + #[cfg(feature = "v1")] /// # Panics /// /// Panics if `json_payload` array does not contain one `GetRefundMetricRequest` element. @@ -712,13 +814,22 @@ pub mod routes { merchant_id: merchant_id.clone(), profile_ids: vec![profile_id.clone()], }; - analytics::refunds::get_metrics(&state.pool, &auth, req) + + let validator_response = request_validator( + AnalyticsRequest { + refund: Some(req.clone()), + ..Default::default() + }, + &state, + ) + .await?; + let ex_rates = validator_response; + analytics::refunds::get_metrics(&state.pool, &ex_rates, &auth, req) .await .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Profile, + permission: Permission::ProfileAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) @@ -752,14 +863,14 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Merchant, + permission: Permission::MerchantAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) .await } + #[cfg(feature = "v1")] /// # Panics /// /// Panics if `json_payload` array does not contain one `GetSdkEventMetricRequest` element. @@ -791,14 +902,14 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Merchant, + permission: Permission::MerchantAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) .await } + #[cfg(feature = "v1")] /// # Panics /// /// Panics if `json_payload` array does not contain one `GetActivePaymentsMetricRequest` element. @@ -831,14 +942,14 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Merchant, + permission: Permission::MerchantAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) .await } + #[cfg(feature = "v1")] /// # Panics /// /// Panics if `json_payload` array does not contain one `GetAuthEventMetricRequest` element. @@ -871,8 +982,7 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Merchant, + permission: Permission::MerchantAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) @@ -902,8 +1012,7 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Merchant, + permission: Permission::MerchantAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) @@ -931,14 +1040,14 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Organization, + permission: Permission::OrganizationAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) .await } + #[cfg(feature = "v1")] pub async fn get_profile_payment_filters( state: web::Data, req: actix_web::HttpRequest, @@ -967,8 +1076,7 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Profile, + permission: Permission::ProfileAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) @@ -996,8 +1104,7 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Merchant, + permission: Permission::MerchantAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) @@ -1027,8 +1134,7 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Merchant, + permission: Permission::MerchantAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) @@ -1056,14 +1162,14 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Organization, + permission: Permission::OrganizationAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) .await } + #[cfg(feature = "v1")] pub async fn get_profile_refund_filters( state: web::Data, req: actix_web::HttpRequest, @@ -1092,8 +1198,7 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Profile, + permission: Permission::ProfileAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) @@ -1117,14 +1222,14 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Merchant, + permission: Permission::MerchantAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) .await } + #[cfg(feature = "v1")] pub async fn get_sdk_event_filters( state: web::Data, req: actix_web::HttpRequest, @@ -1146,8 +1251,7 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Merchant, + permission: Permission::MerchantAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) @@ -1179,8 +1283,7 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Profile, + permission: Permission::ProfileAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) @@ -1213,14 +1316,14 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Profile, + permission: Permission::ProfileAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) .await } + #[cfg(feature = "v1")] pub async fn get_profile_sdk_events( state: web::Data, req: actix_web::HttpRequest, @@ -1245,14 +1348,14 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Profile, + permission: Permission::ProfileAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) .await } + #[cfg(feature = "v1")] pub async fn generate_merchant_refund_report( state: web::Data, req: actix_web::HttpRequest, @@ -1296,14 +1399,14 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::GenerateReport, - minimum_entity_level: EntityType::Merchant, + permission: Permission::MerchantReportRead, }, api_locking::LockAction::NotApplicable, )) .await } + #[cfg(feature = "v1")] pub async fn generate_org_refund_report( state: web::Data, req: actix_web::HttpRequest, @@ -1345,14 +1448,14 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::GenerateReport, - minimum_entity_level: EntityType::Organization, + permission: Permission::OrganizationReportRead, }, api_locking::LockAction::NotApplicable, )) .await } + #[cfg(feature = "v1")] pub async fn generate_profile_refund_report( state: web::Data, req: actix_web::HttpRequest, @@ -1401,14 +1504,13 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::GenerateReport, - minimum_entity_level: EntityType::Profile, + permission: Permission::ProfileReportRead, }, api_locking::LockAction::NotApplicable, )) .await } - + #[cfg(feature = "v1")] pub async fn generate_merchant_dispute_report( state: web::Data, req: actix_web::HttpRequest, @@ -1452,14 +1554,13 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::GenerateReport, - minimum_entity_level: EntityType::Merchant, + permission: Permission::MerchantReportRead, }, api_locking::LockAction::NotApplicable, )) .await } - + #[cfg(feature = "v1")] pub async fn generate_org_dispute_report( state: web::Data, req: actix_web::HttpRequest, @@ -1501,14 +1602,14 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::GenerateReport, - minimum_entity_level: EntityType::Organization, + permission: Permission::OrganizationReportRead, }, api_locking::LockAction::NotApplicable, )) .await } + #[cfg(feature = "v1")] pub async fn generate_profile_dispute_report( state: web::Data, req: actix_web::HttpRequest, @@ -1557,14 +1658,14 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::GenerateReport, - minimum_entity_level: EntityType::Profile, + permission: Permission::ProfileReportRead, }, api_locking::LockAction::NotApplicable, )) .await } + #[cfg(feature = "v1")] pub async fn generate_merchant_payment_report( state: web::Data, req: actix_web::HttpRequest, @@ -1608,14 +1709,13 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::GenerateReport, - minimum_entity_level: EntityType::Merchant, + permission: Permission::MerchantReportRead, }, api_locking::LockAction::NotApplicable, )) .await } - + #[cfg(feature = "v1")] pub async fn generate_org_payment_report( state: web::Data, req: actix_web::HttpRequest, @@ -1657,14 +1757,14 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::GenerateReport, - minimum_entity_level: EntityType::Organization, + permission: Permission::OrganizationReportRead, }, api_locking::LockAction::NotApplicable, )) .await } + #[cfg(feature = "v1")] pub async fn generate_profile_payment_report( state: web::Data, req: actix_web::HttpRequest, @@ -1712,8 +1812,7 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::GenerateReport, - minimum_entity_level: EntityType::Profile, + permission: Permission::ProfileReportRead, }, api_locking::LockAction::NotApplicable, )) @@ -1751,8 +1850,7 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Merchant, + permission: Permission::MerchantAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) @@ -1776,8 +1874,7 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Profile, + permission: Permission::MerchantAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) @@ -1808,8 +1905,7 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Profile, + permission: Permission::ProfileAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) @@ -1829,41 +1925,103 @@ pub mod routes { json_payload.into_inner(), |state, auth: UserFromToken, req, _| async move { let role_id = auth.role_id; - let role_info = RoleInfo::from_role_id_in_merchant_scope( - &state, - &role_id, - &auth.merchant_id, - &auth.org_id, - ) - .await - .change_context(UserErrors::InternalServerError) - .change_context(OpenSearchError::UnknownError)?; - let permissions = role_info.get_permissions_set(); - let accessible_indexes: Vec<_> = OPENSEARCH_INDEX_PERMISSIONS + let role_info = RoleInfo::from_role_id_and_org_id(&state, &role_id, &auth.org_id) + .await + .change_context(UserErrors::InternalServerError) + .change_context(OpenSearchError::UnknownError)?; + let permission_groups = role_info.get_permission_groups(); + if !permission_groups.contains(&common_enums::PermissionGroup::OperationsView) { + return Err(OpenSearchError::AccessForbiddenError)?; + } + let user_roles: HashSet = state + .global_store + .list_user_roles_by_user_id(ListUserRolesByUserIdPayload { + user_id: &auth.user_id, + tenant_id: auth.tenant_id.as_ref().unwrap_or(&state.tenant.tenant_id), + org_id: Some(&auth.org_id), + merchant_id: None, + profile_id: None, + entity_id: None, + version: None, + status: None, + limit: None, + }) + .await + .change_context(UserErrors::InternalServerError) + .change_context(OpenSearchError::UnknownError)? + .into_iter() + .collect(); + + let state = Arc::new(state); + let role_info_map: HashMap = user_roles + .iter() + .map(|user_role| { + let state = Arc::clone(&state); + let role_id = user_role.role_id.clone(); + let org_id = user_role.org_id.clone().unwrap_or_default(); + async move { + RoleInfo::from_role_id_and_org_id(&state, &role_id, &org_id) + .await + .change_context(UserErrors::InternalServerError) + .change_context(OpenSearchError::UnknownError) + .map(|role_info| (role_id, role_info)) + } + }) + .collect::>() + .collect::>() + .await + .into_iter() + .collect::, _>>()?; + + let filtered_user_roles: Vec<&UserRole> = user_roles .iter() - .filter(|(_, perm)| perm.iter().any(|p| permissions.contains(p))) - .map(|(i, _)| *i) + .filter(|user_role| { + let user_role_id = &user_role.role_id; + if let Some(role_info) = role_info_map.get(user_role_id) { + let permissions = role_info.get_permission_groups(); + permissions.contains(&common_enums::PermissionGroup::OperationsView) + } else { + false + } + }) .collect(); - let merchant_id: MerchantId = auth.merchant_id; - let org_id: OrganizationId = auth.org_id; - let search_params: Vec = vec![AuthInfo::MerchantLevel { - org_id: org_id.clone(), - merchant_ids: vec![merchant_id.clone()], - }]; + let search_params: Vec = filtered_user_roles + .iter() + .filter_map(|user_role| { + user_role + .get_entity_id_and_type() + .and_then(|(_, entity_type)| match entity_type { + EntityType::Profile => Some(AuthInfo::ProfileLevel { + org_id: user_role.org_id.clone()?, + merchant_id: user_role.merchant_id.clone()?, + profile_ids: vec![user_role.profile_id.clone()?], + }), + EntityType::Merchant => Some(AuthInfo::MerchantLevel { + org_id: user_role.org_id.clone()?, + merchant_ids: vec![user_role.merchant_id.clone()?], + }), + EntityType::Organization => Some(AuthInfo::OrgLevel { + org_id: user_role.org_id.clone()?, + }), + EntityType::Tenant => Some(AuthInfo::OrgLevel { + org_id: auth.org_id.clone(), + }), + }) + }) + .collect(); analytics::search::msearch_results( &state.opensearch_client, req, search_params, - accessible_indexes, + SEARCH_INDEXES.to_vec(), ) .await .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Profile, + permission: Permission::ProfileAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) @@ -1889,36 +2047,96 @@ pub mod routes { indexed_req, |state, auth: UserFromToken, req, _| async move { let role_id = auth.role_id; - let role_info = RoleInfo::from_role_id_in_merchant_scope( - &state, - &role_id, - &auth.merchant_id, - &auth.org_id, - ) - .await - .change_context(UserErrors::InternalServerError) - .change_context(OpenSearchError::UnknownError)?; - let permissions = role_info.get_permissions_set(); - let _ = OPENSEARCH_INDEX_PERMISSIONS + let role_info = RoleInfo::from_role_id_and_org_id(&state, &role_id, &auth.org_id) + .await + .change_context(UserErrors::InternalServerError) + .change_context(OpenSearchError::UnknownError)?; + let permission_groups = role_info.get_permission_groups(); + if !permission_groups.contains(&common_enums::PermissionGroup::OperationsView) { + return Err(OpenSearchError::AccessForbiddenError)?; + } + let user_roles: HashSet = state + .global_store + .list_user_roles_by_user_id(ListUserRolesByUserIdPayload { + user_id: &auth.user_id, + tenant_id: auth.tenant_id.as_ref().unwrap_or(&state.tenant.tenant_id), + org_id: Some(&auth.org_id), + merchant_id: None, + profile_id: None, + entity_id: None, + version: None, + status: None, + limit: None, + }) + .await + .change_context(UserErrors::InternalServerError) + .change_context(OpenSearchError::UnknownError)? + .into_iter() + .collect(); + let state = Arc::new(state); + let role_info_map: HashMap = user_roles .iter() - .filter(|(ind, _)| *ind == index) - .find(|i| i.1.iter().any(|p| permissions.contains(p))) - .ok_or(OpenSearchError::IndexAccessNotPermittedError(index))?; + .map(|user_role| { + let state = Arc::clone(&state); + let role_id = user_role.role_id.clone(); + let org_id = user_role.org_id.clone().unwrap_or_default(); + async move { + RoleInfo::from_role_id_and_org_id(&state, &role_id, &org_id) + .await + .change_context(UserErrors::InternalServerError) + .change_context(OpenSearchError::UnknownError) + .map(|role_info| (role_id, role_info)) + } + }) + .collect::>() + .collect::>() + .await + .into_iter() + .collect::, _>>()?; - let merchant_id: MerchantId = auth.merchant_id; - let org_id: OrganizationId = auth.org_id; - let search_params: Vec = vec![AuthInfo::MerchantLevel { - org_id: org_id.clone(), - merchant_ids: vec![merchant_id.clone()], - }]; + let filtered_user_roles: Vec<&UserRole> = user_roles + .iter() + .filter(|user_role| { + let user_role_id = &user_role.role_id; + if let Some(role_info) = role_info_map.get(user_role_id) { + let permissions = role_info.get_permission_groups(); + permissions.contains(&common_enums::PermissionGroup::OperationsView) + } else { + false + } + }) + .collect(); + let search_params: Vec = filtered_user_roles + .iter() + .filter_map(|user_role| { + user_role + .get_entity_id_and_type() + .and_then(|(_, entity_type)| match entity_type { + EntityType::Profile => Some(AuthInfo::ProfileLevel { + org_id: user_role.org_id.clone()?, + merchant_id: user_role.merchant_id.clone()?, + profile_ids: vec![user_role.profile_id.clone()?], + }), + EntityType::Merchant => Some(AuthInfo::MerchantLevel { + org_id: user_role.org_id.clone()?, + merchant_ids: vec![user_role.merchant_id.clone()?], + }), + EntityType::Organization => Some(AuthInfo::OrgLevel { + org_id: user_role.org_id.clone()?, + }), + EntityType::Tenant => Some(AuthInfo::OrgLevel { + org_id: auth.org_id.clone(), + }), + }) + }) + .collect(); analytics::search::search_results(&state.opensearch_client, req, search_params) .await .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Profile, + permission: Permission::ProfileAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) @@ -1948,14 +2166,14 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Merchant, + permission: Permission::MerchantAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) .await } + #[cfg(feature = "v1")] pub async fn get_profile_dispute_filters( state: web::Data, req: actix_web::HttpRequest, @@ -1984,8 +2202,7 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Profile, + permission: Permission::ProfileAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) @@ -2013,8 +2230,7 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Organization, + permission: Permission::OrganizationAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) @@ -2054,14 +2270,14 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Merchant, + permission: Permission::MerchantAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) .await } + #[cfg(feature = "v1")] /// # Panics /// /// Panics if `json_payload` array does not contain one `GetDisputeMetricRequest` element. @@ -2100,8 +2316,7 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Profile, + permission: Permission::ProfileAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) @@ -2139,8 +2354,104 @@ pub mod routes { .map(ApplicationResponse::Json) }, &auth::JWTAuth { - permission: Permission::Analytics, - minimum_entity_level: EntityType::Organization, + permission: Permission::OrganizationAnalyticsRead, + }, + api_locking::LockAction::NotApplicable, + )) + .await + } + + pub async fn get_merchant_sankey( + state: web::Data, + req: actix_web::HttpRequest, + json_payload: web::Json, + ) -> impl Responder { + let flow = AnalyticsFlow::GetSankey; + let payload = json_payload.into_inner(); + Box::pin(api::server_wrap( + flow, + state, + &req, + payload, + |state, auth: AuthenticationData, req, _| async move { + let org_id = auth.merchant_account.get_org_id(); + let merchant_id = auth.merchant_account.get_id(); + let auth: AuthInfo = AuthInfo::MerchantLevel { + org_id: org_id.clone(), + merchant_ids: vec![merchant_id.clone()], + }; + analytics::payment_intents::get_sankey(&state.pool, &auth, req) + .await + .map(ApplicationResponse::Json) + }, + &auth::JWTAuth { + permission: Permission::MerchantAnalyticsRead, + }, + api_locking::LockAction::NotApplicable, + )) + .await + } + + pub async fn get_org_sankey( + state: web::Data, + req: actix_web::HttpRequest, + json_payload: web::Json, + ) -> impl Responder { + let flow = AnalyticsFlow::GetSankey; + let payload = json_payload.into_inner(); + Box::pin(api::server_wrap( + flow, + state, + &req, + payload, + |state, auth: AuthenticationData, req, _| async move { + let org_id = auth.merchant_account.get_org_id(); + let auth: AuthInfo = AuthInfo::OrgLevel { + org_id: org_id.clone(), + }; + analytics::payment_intents::get_sankey(&state.pool, &auth, req) + .await + .map(ApplicationResponse::Json) + }, + &auth::JWTAuth { + permission: Permission::OrganizationAnalyticsRead, + }, + api_locking::LockAction::NotApplicable, + )) + .await + } + + #[cfg(feature = "v1")] + pub async fn get_profile_sankey( + state: web::Data, + req: actix_web::HttpRequest, + json_payload: web::Json, + ) -> impl Responder { + let flow = AnalyticsFlow::GetSankey; + let payload = json_payload.into_inner(); + Box::pin(api::server_wrap( + flow, + state, + &req, + payload, + |state: crate::routes::SessionState, auth: AuthenticationData, req, _| async move { + let org_id = auth.merchant_account.get_org_id(); + let merchant_id = auth.merchant_account.get_id(); + let profile_id = auth + .profile_id + .ok_or(report!(UserErrors::JwtProfileIdMissing)) + .change_context(AnalyticsError::AccessForbiddenError)?; + let auth: AuthInfo = AuthInfo::ProfileLevel { + org_id: org_id.clone(), + merchant_id: merchant_id.clone(), + profile_ids: vec![profile_id.clone()], + }; + analytics::payment_intents::get_sankey(&state.pool, &auth, req) + .await + .map(ApplicationResponse::Json) + }, + &auth::JWTAuth { + permission: Permission::ProfileAnalyticsRead, }, api_locking::LockAction::NotApplicable, )) diff --git a/crates/router/src/analytics_validator.rs b/crates/router/src/analytics_validator.rs new file mode 100644 index 000000000000..d50308cc848c --- /dev/null +++ b/crates/router/src/analytics_validator.rs @@ -0,0 +1,24 @@ +use analytics::errors::AnalyticsError; +use api_models::analytics::AnalyticsRequest; +use common_utils::errors::CustomResult; +use currency_conversion::types::ExchangeRates; +use router_env::logger; + +use crate::core::currency::get_forex_exchange_rates; + +pub async fn request_validator( + req_type: AnalyticsRequest, + state: &crate::routes::SessionState, +) -> CustomResult, AnalyticsError> { + let forex_enabled = state.conf.analytics.get_inner().get_forex_enabled(); + let require_forex_functionality = req_type.requires_forex_functionality(); + + let ex_rates = if forex_enabled && require_forex_functionality { + logger::info!("Fetching forex exchange rates"); + Some(get_forex_exchange_rates(state.clone()).await?) + } else { + None + }; + + Ok(ex_rates) +} diff --git a/crates/router/src/bin/router.rs b/crates/router/src/bin/router.rs index 5b6dd81501b8..212a12574252 100644 --- a/crates/router/src/bin/router.rs +++ b/crates/router/src/bin/router.rs @@ -35,7 +35,7 @@ async fn main() -> ApplicationResult<()> { // Spawn a thread for collecting metrics at fixed intervals metrics::bg_metrics_collector::spawn_metrics_collector( - &conf.log.telemetry.bg_metrics_collection_interval_in_secs, + conf.log.telemetry.bg_metrics_collection_interval_in_secs, ); #[allow(clippy::expect_used)] diff --git a/crates/router/src/compatibility/stripe/customers.rs b/crates/router/src/compatibility/stripe/customers.rs index 7cecf49496b3..56cf25434c29 100644 --- a/crates/router/src/compatibility/stripe/customers.rs +++ b/crates/router/src/compatibility/stripe/customers.rs @@ -54,7 +54,7 @@ pub async fn customer_create( state.into_inner(), &req, create_cust_req, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { customers::create_customer(state, auth.merchant_account, auth.key_store, req) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -74,7 +74,7 @@ pub async fn customer_retrieve( req: HttpRequest, path: web::Path, ) -> HttpResponse { - let payload = customer_types::CustomerId::new_customer_id_struct(path.into_inner()); + let customer_id = path.into_inner(); let flow = Flow::CustomersRetrieve; @@ -91,9 +91,15 @@ pub async fn customer_retrieve( flow, state.into_inner(), &req, - payload, - |state, auth, req, _| { - customers::retrieve_customer(state, auth.merchant_account, None, auth.key_store, req) + customer_id, + |state, auth: auth::AuthenticationData, customer_id, _| { + customers::retrieve_customer( + state, + auth.merchant_account, + None, + auth.key_store, + customer_id, + ) }, &auth::HeaderAuth(auth::ApiKeyAuth), api_locking::LockAction::NotApplicable, @@ -121,10 +127,12 @@ pub async fn customer_update( } }; - let customer_id = path.into_inner(); - let mut cust_update_req: customer_types::CustomerUpdateRequest = payload.into(); - cust_update_req.customer_id = Some(customer_id); - let customer_update_id = customer_types::UpdateCustomerId::new("temp_global_id".to_string()); + let customer_id = path.into_inner().clone(); + let request = customer_types::CustomerUpdateRequest::from(payload); + let request_internal = customer_types::CustomerUpdateRequestInternal { + customer_id, + request, + }; let flow = Flow::CustomersUpdate; @@ -141,14 +149,13 @@ pub async fn customer_update( flow, state.into_inner(), &req, - cust_update_req, - |state, auth, req, _| { + request_internal, + |state, auth: auth::AuthenticationData, request_internal, _| { customers::update_customer( state, auth.merchant_account, - req, + request_internal, auth.key_store, - customer_update_id.clone(), ) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -168,7 +175,7 @@ pub async fn customer_delete( req: HttpRequest, path: web::Path, ) -> HttpResponse { - let payload = customer_types::CustomerId::new_customer_id_struct(path.into_inner()); + let customer_id = path.into_inner(); let flow = Flow::CustomersDelete; @@ -185,9 +192,9 @@ pub async fn customer_delete( flow, state.into_inner(), &req, - payload, - |state, auth: auth::AuthenticationData, req, _| { - customers::delete_customer(state, auth.merchant_account, req, auth.key_store) + customer_id, + |state, auth: auth::AuthenticationData, customer_id, _| { + customers::delete_customer(state, auth.merchant_account, customer_id, auth.key_store) }, &auth::HeaderAuth(auth::ApiKeyAuth), api_locking::LockAction::NotApplicable, @@ -225,7 +232,7 @@ pub async fn list_customer_payment_method_api( state.into_inner(), &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { cards::do_list_customer_pm_fetch_customer_if_not_passed( state, auth.merchant_account, diff --git a/crates/router/src/compatibility/stripe/errors.rs b/crates/router/src/compatibility/stripe/errors.rs index efe0ac157e01..630d4dfdca0e 100644 --- a/crates/router/src/compatibility/stripe/errors.rs +++ b/crates/router/src/compatibility/stripe/errors.rs @@ -278,6 +278,10 @@ pub enum StripeErrorCode { InvalidTenant, #[error(error_type = StripeErrorType::HyperswitchError, code = "HE_01", message = "Failed to convert amount to {amount_type} type")] AmountConversionFailed { amount_type: &'static str }, + #[error(error_type = StripeErrorType::HyperswitchError, code = "", message = "Platform Bad Request")] + PlatformBadRequest, + #[error(error_type = StripeErrorType::HyperswitchError, code = "", message = "Platform Unauthorized Request")] + PlatformUnauthorizedRequest, // [#216]: https://github.com/juspay/hyperswitch/issues/216 // Implement the remaining stripe error codes @@ -444,7 +448,8 @@ impl From for StripeErrorCode { | errors::ApiErrorResponse::GenericUnauthorized { .. } | errors::ApiErrorResponse::AccessForbidden { .. } | errors::ApiErrorResponse::InvalidCookie - | errors::ApiErrorResponse::InvalidEphemeralKey => Self::Unauthorized, + | errors::ApiErrorResponse::InvalidEphemeralKey + | errors::ApiErrorResponse::CookieNotFound => Self::Unauthorized, errors::ApiErrorResponse::InvalidRequestUrl | errors::ApiErrorResponse::InvalidHttpMethod | errors::ApiErrorResponse::InvalidCardIin @@ -677,6 +682,8 @@ impl From for StripeErrorCode { errors::ApiErrorResponse::AmountConversionFailed { amount_type } => { Self::AmountConversionFailed { amount_type } } + errors::ApiErrorResponse::PlatformAccountAuthNotSupported => Self::PlatformBadRequest, + errors::ApiErrorResponse::InvalidPlatformOperation => Self::PlatformUnauthorizedRequest, } } } @@ -686,7 +693,7 @@ impl actix_web::ResponseError for StripeErrorCode { use reqwest::StatusCode; match self { - Self::Unauthorized => StatusCode::UNAUTHORIZED, + Self::Unauthorized | Self::PlatformUnauthorizedRequest => StatusCode::UNAUTHORIZED, Self::InvalidRequestUrl | Self::GenericNotFoundError { .. } => StatusCode::NOT_FOUND, Self::ParameterUnknown { .. } | Self::HyperswitchUnprocessableEntity { .. } => { StatusCode::UNPROCESSABLE_ENTITY @@ -750,6 +757,7 @@ impl actix_web::ResponseError for StripeErrorCode { | Self::CurrencyConversionFailed | Self::PaymentMethodDeleteFailed | Self::ExtendedCardInfoNotFound + | Self::PlatformBadRequest | Self::LinkConfigurationError { .. } => StatusCode::BAD_REQUEST, Self::RefundFailed | Self::PayoutFailed diff --git a/crates/router/src/compatibility/stripe/payment_intents.rs b/crates/router/src/compatibility/stripe/payment_intents.rs index ec3dd9cc9536..6652f42ee00e 100644 --- a/crates/router/src/compatibility/stripe/payment_intents.rs +++ b/crates/router/src/compatibility/stripe/payment_intents.rs @@ -71,7 +71,7 @@ pub async fn payment_intents_create( state.into_inner(), &req, create_payment_req, - |state, auth, req, req_state| { + |state, auth: auth::AuthenticationData, req, req_state| { let eligible_connectors = req.connector.clone(); payments::payments_core::< api_types::Authorize, @@ -91,7 +91,8 @@ pub async fn payment_intents_create( api::AuthFlow::Merchant, payments::CallConnectorAction::Trigger, eligible_connectors, - api_types::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), + auth.platform_merchant_account, ) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -100,7 +101,7 @@ pub async fn payment_intents_create( .await } -#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] +#[cfg(feature = "v1")] #[instrument(skip_all, fields(flow = ?Flow::PaymentsRetrieveForceSync))] pub async fn payment_intents_retrieve( state: web::Data, @@ -161,7 +162,8 @@ pub async fn payment_intents_retrieve( auth_flow, payments::CallConnectorAction::Trigger, None, - api_types::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), + auth.platform_merchant_account, ) }, &*auth_type, @@ -239,7 +241,8 @@ pub async fn payment_intents_retrieve_with_gateway_creds( api::AuthFlow::Merchant, payments::CallConnectorAction::Trigger, None, - api_types::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), + auth.platform_merchant_account, ) }, &*auth_type, @@ -315,7 +318,8 @@ pub async fn payment_intents_update( auth_flow, payments::CallConnectorAction::Trigger, eligible_connectors, - api_types::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), + auth.platform_merchant_account, ) }, &*auth_type, @@ -400,7 +404,8 @@ pub async fn payment_intents_confirm( auth_flow, payments::CallConnectorAction::Trigger, eligible_connectors, - api_types::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), + auth.platform_merchant_account, ) }, &*auth_type, @@ -452,7 +457,7 @@ pub async fn payment_intents_capture( state.into_inner(), &req, payload, - |state, auth, payload, req_state| { + |state, auth: auth::AuthenticationData, payload, req_state| { payments::payments_core::< api_types::Capture, api_types::PaymentsResponse, @@ -471,7 +476,8 @@ pub async fn payment_intents_capture( api::AuthFlow::Merchant, payments::CallConnectorAction::Trigger, None, - api_types::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), + auth.platform_merchant_account, ) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -546,7 +552,8 @@ pub async fn payment_intents_cancel( auth_flow, payments::CallConnectorAction::Trigger, None, - api_types::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), + auth.platform_merchant_account, ) }, &*auth_type, @@ -581,7 +588,7 @@ pub async fn payment_intent_list( state.into_inner(), &req, payload, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { payments::list_payments(state, auth.merchant_account, None, auth.key_store, req) }, &auth::HeaderAuth(auth::ApiKeyAuth), diff --git a/crates/router/src/compatibility/stripe/payment_intents/types.rs b/crates/router/src/compatibility/stripe/payment_intents/types.rs index f0e4b1169c81..545c058ce1cd 100644 --- a/crates/router/src/compatibility/stripe/payment_intents/types.rs +++ b/crates/router/src/compatibility/stripe/payment_intents/types.rs @@ -321,9 +321,10 @@ impl TryFrom for payments::PaymentsRequest { let billing = pmd.billing_details.clone().map(payments::Address::from); let payment_method_data = match pmd.payment_method_details.as_ref() { Some(spmd) => Some(payments::PaymentMethodData::from(spmd.to_owned())), - None => { - get_pmd_based_on_payment_method_type(item.payment_method_types, billing.clone()) - } + None => get_pmd_based_on_payment_method_type( + item.payment_method_types, + billing.clone().map(From::from), + ), }; payments::PaymentMethodDataRequest { @@ -837,6 +838,9 @@ pub enum StripeNextAction { InvokeSdkClient { next_action_data: payments::SdkNextActionData, }, + CollectOtp { + consent_data_required: payments::MobilePaymentConsent, + }, } pub(crate) fn into_stripe_next_action( @@ -892,6 +896,11 @@ pub(crate) fn into_stripe_next_action( payments::NextActionData::InvokeSdkClient { next_action_data } => { StripeNextAction::InvokeSdkClient { next_action_data } } + payments::NextActionData::CollectOtp { + consent_data_required, + } => StripeNextAction::CollectOtp { + consent_data_required, + }, }) } @@ -903,7 +912,7 @@ pub struct StripePaymentRetrieveBody { //To handle payment types that have empty payment method data fn get_pmd_based_on_payment_method_type( payment_method_type: Option, - billing_details: Option, + billing_details: Option, ) -> Option { match payment_method_type { Some(api_enums::PaymentMethodType::UpiIntent) => Some(payments::PaymentMethodData::Upi( diff --git a/crates/router/src/compatibility/stripe/refunds.rs b/crates/router/src/compatibility/stripe/refunds.rs index 09585cd9c3af..63141f7311c0 100644 --- a/crates/router/src/compatibility/stripe/refunds.rs +++ b/crates/router/src/compatibility/stripe/refunds.rs @@ -49,7 +49,7 @@ pub async fn refund_create( state.into_inner(), &req, create_refund_req, - |state, auth, req, _| { + |state, auth: auth::AuthenticationData, req, _| { refunds::refund_create_core(state, auth.merchant_account, None, auth.key_store, req) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -93,7 +93,7 @@ pub async fn refund_retrieve_with_gateway_creds( state.into_inner(), &req, refund_request, - |state, auth, refund_request, _| { + |state, auth: auth::AuthenticationData, refund_request, _| { refunds::refund_response_wrapper( state, auth.merchant_account, @@ -136,7 +136,7 @@ pub async fn refund_retrieve( state.into_inner(), &req, refund_request, - |state, auth, refund_request, _| { + |state, auth: auth::AuthenticationData, refund_request, _| { refunds::refund_response_wrapper( state, auth.merchant_account, @@ -177,7 +177,9 @@ pub async fn refund_update( state.into_inner(), &req, create_refund_update_req, - |state, auth, req, _| refunds::refund_update_core(state, auth.merchant_account, req), + |state, auth: auth::AuthenticationData, req, _| { + refunds::refund_update_core(state, auth.merchant_account, req) + }, &auth::HeaderAuth(auth::ApiKeyAuth), api_locking::LockAction::NotApplicable, )) diff --git a/crates/router/src/compatibility/stripe/setup_intents.rs b/crates/router/src/compatibility/stripe/setup_intents.rs index fbd2fa812074..6dde49b0d620 100644 --- a/crates/router/src/compatibility/stripe/setup_intents.rs +++ b/crates/router/src/compatibility/stripe/setup_intents.rs @@ -58,7 +58,7 @@ pub async fn setup_intents_create( state.into_inner(), &req, create_payment_req, - |state, auth, req, req_state| { + |state, auth: auth::AuthenticationData, req, req_state| { payments::payments_core::< api_types::SetupMandate, api_types::PaymentsResponse, @@ -77,7 +77,8 @@ pub async fn setup_intents_create( api::AuthFlow::Merchant, payments::CallConnectorAction::Trigger, None, - api_types::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), + auth.platform_merchant_account, ) }, &auth::HeaderAuth(auth::ApiKeyAuth), @@ -86,7 +87,7 @@ pub async fn setup_intents_create( .await } -#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] +#[cfg(feature = "v1")] #[instrument(skip_all, fields(flow = ?Flow::PaymentsRetrieveForceSync))] pub async fn setup_intents_retrieve( state: web::Data, @@ -147,7 +148,8 @@ pub async fn setup_intents_retrieve( auth_flow, payments::CallConnectorAction::Trigger, None, - api_types::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), + auth.platform_merchant_account, ) }, &*auth_type, @@ -223,7 +225,8 @@ pub async fn setup_intents_update( auth_flow, payments::CallConnectorAction::Trigger, None, - api_types::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), + auth.platform_merchant_account, ) }, &*auth_type, @@ -300,7 +303,8 @@ pub async fn setup_intents_confirm( auth_flow, payments::CallConnectorAction::Trigger, None, - api_types::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), + auth.platform_merchant_account, ) }, &*auth_type, diff --git a/crates/router/src/compatibility/stripe/setup_intents/types.rs b/crates/router/src/compatibility/stripe/setup_intents/types.rs index 58a4d95b28f7..03cf9742f704 100644 --- a/crates/router/src/compatibility/stripe/setup_intents/types.rs +++ b/crates/router/src/compatibility/stripe/setup_intents/types.rs @@ -391,6 +391,9 @@ pub enum StripeNextAction { InvokeSdkClient { next_action_data: payments::SdkNextActionData, }, + CollectOtp { + consent_data_required: payments::MobilePaymentConsent, + }, } pub(crate) fn into_stripe_next_action( @@ -446,6 +449,11 @@ pub(crate) fn into_stripe_next_action( payments::NextActionData::InvokeSdkClient { next_action_data } => { StripeNextAction::InvokeSdkClient { next_action_data } } + payments::NextActionData::CollectOtp { + consent_data_required, + } => StripeNextAction::CollectOtp { + consent_data_required, + }, }) } diff --git a/crates/router/src/compatibility/stripe/webhooks.rs b/crates/router/src/compatibility/stripe/webhooks.rs index 8445011f569f..2212a8953fc7 100644 --- a/crates/router/src/compatibility/stripe/webhooks.rs +++ b/crates/router/src/compatibility/stripe/webhooks.rs @@ -1,7 +1,7 @@ #[cfg(feature = "payouts")] use api_models::payouts as payout_models; use api_models::{ - enums::{DisputeStatus, MandateStatus}, + enums::{Currency, DisputeStatus, MandateStatus}, webhooks::{self as api}, }; #[cfg(feature = "payouts")] @@ -81,7 +81,7 @@ impl OutgoingWebhookType for StripeOutgoingWebhook { #[derive(Serialize, Debug)] #[serde(tag = "type", content = "object", rename_all = "snake_case")] pub enum StripeWebhookObject { - PaymentIntent(StripePaymentIntentResponse), + PaymentIntent(Box), Refund(StripeRefundResponse), Dispute(StripeDisputeResponse), Mandate(StripeMandateResponse), @@ -93,7 +93,7 @@ pub enum StripeWebhookObject { pub struct StripeDisputeResponse { pub id: String, pub amount: String, - pub currency: String, + pub currency: Currency, pub payment_intent: common_utils::id_type::PaymentId, pub reason: Option, pub status: StripeDisputeStatus, @@ -327,9 +327,9 @@ impl From for StripeWebhookObject { fn from(value: api::OutgoingWebhookContent) -> Self { match value { api::OutgoingWebhookContent::PaymentDetails(payment) => { - Self::PaymentIntent(payment.into()) + Self::PaymentIntent(Box::new((*payment).into())) } - api::OutgoingWebhookContent::RefundDetails(refund) => Self::Refund(refund.into()), + api::OutgoingWebhookContent::RefundDetails(refund) => Self::Refund((*refund).into()), api::OutgoingWebhookContent::DisputeDetails(dispute) => { Self::Dispute((*dispute).into()) } @@ -337,7 +337,7 @@ impl From for StripeWebhookObject { Self::Mandate((*mandate).into()) } #[cfg(feature = "payouts")] - api::OutgoingWebhookContent::PayoutDetails(payout) => Self::Payout(payout.into()), + api::OutgoingWebhookContent::PayoutDetails(payout) => Self::Payout((*payout).into()), } } } diff --git a/crates/router/src/configs/defaults.rs b/crates/router/src/configs/defaults.rs index 254a168eb33d..74f6502159da 100644 --- a/crates/router/src/configs/defaults.rs +++ b/crates/router/src/configs/defaults.rs @@ -2,11 +2,11 @@ use std::collections::{HashMap, HashSet}; use api_models::{enums, payment_methods::RequiredFieldInfo}; -use super::settings::{ConnectorFields, PaymentMethodType, RequiredFieldFinal}; - #[cfg(feature = "payouts")] pub mod payout_required_fields; +pub mod payment_connector_required_fields; + impl Default for super::settings::Server { fn default() -> Self { Self { @@ -138,12365 +138,6 @@ impl Default for super::settings::KvConfig { } } -use super::settings::{ - Mandates, SupportedConnectorsForMandate, SupportedPaymentMethodTypesForMandate, - SupportedPaymentMethodsForMandate, -}; - -impl Default for Mandates { - fn default() -> Self { - Self { - supported_payment_methods: SupportedPaymentMethodsForMandate(HashMap::from([ - ( - enums::PaymentMethod::PayLater, - SupportedPaymentMethodTypesForMandate(HashMap::from([( - enums::PaymentMethodType::Klarna, - SupportedConnectorsForMandate { - connector_list: HashSet::from([enums::Connector::Adyen]), - }, - )])), - ), - ( - enums::PaymentMethod::Wallet, - SupportedPaymentMethodTypesForMandate(HashMap::from([ - ( - enums::PaymentMethodType::GooglePay, - SupportedConnectorsForMandate { - connector_list: HashSet::from([ - enums::Connector::Stripe, - enums::Connector::Adyen, - enums::Connector::Globalpay, - enums::Connector::Multisafepay, - enums::Connector::Bankofamerica, - enums::Connector::Noon, - enums::Connector::Cybersource, - enums::Connector::Wellsfargo, - ]), - }, - ), - ( - enums::PaymentMethodType::ApplePay, - SupportedConnectorsForMandate { - connector_list: HashSet::from([ - enums::Connector::Stripe, - enums::Connector::Adyen, - enums::Connector::Bankofamerica, - enums::Connector::Cybersource, - enums::Connector::Wellsfargo, - ]), - }, - ), - ])), - ), - ( - enums::PaymentMethod::Card, - SupportedPaymentMethodTypesForMandate(HashMap::from([ - ( - enums::PaymentMethodType::Credit, - SupportedConnectorsForMandate { - connector_list: HashSet::from([ - enums::Connector::Aci, - enums::Connector::Adyen, - enums::Connector::Authorizedotnet, - enums::Connector::Globalpay, - enums::Connector::Worldpay, - enums::Connector::Multisafepay, - enums::Connector::Nexinets, - enums::Connector::Noon, - enums::Connector::Payme, - enums::Connector::Stripe, - enums::Connector::Bankofamerica, - enums::Connector::Cybersource, - enums::Connector::Wellsfargo, - ]), - }, - ), - ( - enums::PaymentMethodType::Debit, - SupportedConnectorsForMandate { - connector_list: HashSet::from([ - enums::Connector::Aci, - enums::Connector::Adyen, - enums::Connector::Authorizedotnet, - enums::Connector::Globalpay, - enums::Connector::Worldpay, - enums::Connector::Multisafepay, - enums::Connector::Nexinets, - enums::Connector::Noon, - enums::Connector::Payme, - enums::Connector::Stripe, - ]), - }, - ), - ])), - ), - ])), - update_mandate_supported: SupportedPaymentMethodsForMandate(HashMap::default()), - } - } -} - -impl Default for super::settings::RequiredFields { - fn default() -> Self { - Self(HashMap::from([ - ( - enums::PaymentMethod::Card, - PaymentMethodType(HashMap::from([ - ( - enums::PaymentMethodType::Debit, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Aci, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ] - ), - } - ), - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ] - ), - } - ), - ( - enums::Connector::Airwallex, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Authorizedotnet, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - ( - enums::Connector::Bambora, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Bankofamerica, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "email".to_string(), - RequiredFieldInfo { - required_field: "email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.state".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.state".to_string(), - display_name: "state".to_string(), - field_type: enums::FieldType::UserAddressState, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Billwerk, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - ( - enums::Connector::Bluesnap, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "email".to_string(), - RequiredFieldInfo { - required_field: "email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Boku, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - ( - enums::Connector::Braintree, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - ( - enums::Connector::Checkout, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Coinbase, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Cybersource, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common:HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.state".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.state".to_string(), - display_name: "state".to_string(), - field_type: enums::FieldType::UserAddressState, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ) - ] - ), - } - ), - ( - enums::Connector::Dlocal, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ) - ] - ), - common:HashMap::new(), - } - ), - #[cfg(feature = "dummy_connector")] - ( - enums::Connector::DummyConnector1, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - #[cfg(feature = "dummy_connector")] - ( - enums::Connector::DummyConnector2, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - #[cfg(feature = "dummy_connector")] - ( - enums::Connector::DummyConnector3, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - #[cfg(feature = "dummy_connector")] - ( - enums::Connector::DummyConnector4, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - #[cfg(feature = "dummy_connector")] - ( - enums::Connector::DummyConnector5, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - #[cfg(feature = "dummy_connector")] - ( - enums::Connector::DummyConnector6, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - #[cfg(feature = "dummy_connector")] - ( - enums::Connector::DummyConnector7, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - ( - enums::Connector::Fiserv, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Fiuu, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Forte, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ] - ), - common:HashMap::new(), - } - ), - ( - enums::Connector::Globalpay, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from([ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ]), - } - ), - ( - enums::Connector::Helcim, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Iatapay, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Mollie, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Multisafepay, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate:HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "billing.address.line2".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line2".to_string(), - display_name: "line2".to_string(), - field_type: enums::FieldType::UserAddressLine2, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ) - ] - ), - } - ), - ( - enums::Connector::Nexinets, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - ( - enums::Connector::Nmi, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "billing_zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Noon, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ] - ), - } - ), - ( - enums::Connector::Novalnet, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "browser_info.language".to_string(), - RequiredFieldInfo { - required_field: "browser_info.language".to_string(), - display_name: "browser_info_language".to_string(), - field_type: enums::FieldType::BrowserLanguage, - value: None, - } - ), - ( - "browser_info.ip_address".to_string(), - RequiredFieldInfo { - required_field: "browser_info.ip_address".to_string(), - display_name: "browser_info_ip_address".to_string(), - field_type: enums::FieldType::BrowserIp, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "billing.address.line2".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line2".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine2, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "first_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "last_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email_address".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ] - ), - } - ), - ( - enums::Connector::Nuvei, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - ( - enums::Connector::Paybox, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "email".to_string(), - RequiredFieldInfo { - required_field: "email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ) - - ] - ), - } - ), - ( - enums::Connector::Payme, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "email".to_string(), - RequiredFieldInfo { - required_field: "email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ] - ), - } - ), - ( - enums::Connector::Paypal, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Payu, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Powertranz, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Rapyd, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Shift4, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Square, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - ( - enums::Connector::Stax, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Stripe, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common:HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - ( - enums::Connector::Trustpay, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ), - ] - ), - common: HashMap::new() - } - ), - ( - enums::Connector::Tsys, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - common: HashMap::new() - } - ), - ( - enums::Connector::Wellsfargo, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common:HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "email".to_string(), - RequiredFieldInfo { - required_field: "email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.state".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.state".to_string(), - display_name: "state".to_string(), - field_type: enums::FieldType::UserAddressState, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ) - ] - ), - } - ), - ( - enums::Connector::Worldline, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ) - ]), - common: HashMap::new(), - } - ), - ( - enums::Connector::Worldpay, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ) - ]), - common: HashMap::new(), - } - ), - ( - enums::Connector::Zen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "email".to_string(), - RequiredFieldInfo { - required_field: "email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ]), - common: HashMap::new(), - } - ), - ]), - }, - ), - ( - enums::PaymentMethodType::Credit, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Aci, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ] - ), - } - ), - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ] - ), - } - ), - ( - enums::Connector::Airwallex, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Authorizedotnet, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - ( - enums::Connector::Bambora, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Bankofamerica, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "email".to_string(), - RequiredFieldInfo { - required_field: "email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.state".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.state".to_string(), - display_name: "state".to_string(), - field_type: enums::FieldType::UserAddressState, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ] - ), - } - ), - ( - enums::Connector::Billwerk, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - ( - enums::Connector::Bluesnap, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "email".to_string(), - RequiredFieldInfo { - required_field: "email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Boku, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - ( - enums::Connector::Braintree, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - ( - enums::Connector::Checkout, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Coinbase, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Cybersource, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate:HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.state".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.state".to_string(), - display_name: "state".to_string(), - field_type: enums::FieldType::UserAddressState, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ) - ] - ), - } - ), - ( - enums::Connector::Dlocal, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ) - ] - ), - common:HashMap::new(), - } - ), - #[cfg(feature = "dummy_connector")] - ( - enums::Connector::DummyConnector1, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - #[cfg(feature = "dummy_connector")] - ( - enums::Connector::DummyConnector2, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - #[cfg(feature = "dummy_connector")] - ( - enums::Connector::DummyConnector3, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - #[cfg(feature = "dummy_connector")] - ( - enums::Connector::DummyConnector4, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - #[cfg(feature = "dummy_connector")] - ( - enums::Connector::DummyConnector5, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - #[cfg(feature = "dummy_connector")] - ( - enums::Connector::DummyConnector6, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - #[cfg(feature = "dummy_connector")] - ( - enums::Connector::DummyConnector7, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - ( - enums::Connector::Fiserv, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Fiuu, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Forte, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ] - ), - common:HashMap::new(), - } - ), - ( - enums::Connector::Globalpay, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from([ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ]), - } - ), - ( - enums::Connector::Helcim, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Iatapay, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Mollie, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Multisafepay, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate:HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "billing.address.line2".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line2".to_string(), - display_name: "line2".to_string(), - field_type: enums::FieldType::UserAddressLine2, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ) - ] - ), - } - ), - ( - enums::Connector::Nexinets, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - ( - enums::Connector::Nmi, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "billing_zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Noon, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ] - ), - } - ), - ( - enums::Connector::Novalnet, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "browser_info.language".to_string(), - RequiredFieldInfo { - required_field: "browser_info.language".to_string(), - display_name: "browser_info_language".to_string(), - field_type: enums::FieldType::BrowserLanguage, - value: None, - } - ), - ( - "browser_info.ip_address".to_string(), - RequiredFieldInfo { - required_field: "browser_info.ip_address".to_string(), - display_name: "browser_info_ip_address".to_string(), - field_type: enums::FieldType::BrowserIp, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "billing.address.line2".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line2".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine2, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "first_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "last_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email_address".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ] - ), - } - ), - ( - enums::Connector::Nuvei, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ),( - enums::Connector::Paybox, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "email".to_string(), - RequiredFieldInfo { - required_field: "email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ) - - ] - ), - } - ), - ( - enums::Connector::Payme, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "email".to_string(), - RequiredFieldInfo { - required_field: "email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ] - ), - } - ), - ( - enums::Connector::Paypal, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Payu, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Powertranz, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Rapyd, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Shift4, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Square, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - ( - enums::Connector::Stax, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Stripe, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common:HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - } - ), - ( - enums::Connector::Trustpay, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ), - ] - ), - common: HashMap::new() - } - ), - ( - enums::Connector::Tsys, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ) - ] - ), - common: HashMap::new() - } - ), - ( - enums::Connector::Wellsfargo, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate:HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "email".to_string(), - RequiredFieldInfo { - required_field: "email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.state".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.state".to_string(), - display_name: "state".to_string(), - field_type: enums::FieldType::UserAddressState, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ) - ] - ), - } - ), - ( - enums::Connector::Worldline, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "payment_method_data.card.card_cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_cvc".to_string(), - display_name: "card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ) - ]), - common: HashMap::new(), - } - ), - ( - enums::Connector::Worldpay, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ) - ]), - common: HashMap::new(), - } - ), - ( - enums::Connector::Zen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "payment_method_data.card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "email".to_string(), - RequiredFieldInfo { - required_field: "email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ]), - common: HashMap::new(), - } - ), - ]), - }, - ), - - ])), - ), - ( - enums::PaymentMethod::BankRedirect, - PaymentMethodType(HashMap::from([ - ( - enums::PaymentMethodType::OpenBankingUk, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Volt, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "billing_first_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "billing_last_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ]), - common: HashMap::new(), - } - ), - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap:: from([ - ( - "payment_method_data.bank_redirect.open_banking_uk.issuer".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.open_banking_uk.issuer".to_string(), - display_name: "issuer".to_string(), - field_type: enums::FieldType::UserBank, - value: None, - } - ) - ]), - common: HashMap::new(), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::Trustly, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::OnlineBankingCzechRepublic, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "payment_method_data.bank_redirect.open_banking_czech_republic.issuer".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.open_banking_czech_republic.issuer".to_string(), - display_name: "issuer".to_string(), - field_type: enums::FieldType::UserBank, - value: None, - } - ) - ]), - common: HashMap::new(), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::OnlineBankingFinland, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ]), - common: HashMap::new(), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::OnlineBankingPoland, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "payment_method_data.bank_redirect.open_banking_poland.issuer".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.open_banking_poland.issuer".to_string(), - display_name: "issuer".to_string(), - field_type: enums::FieldType::UserBank, - value: None, - } - ), - - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ]), - common: HashMap::new(), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::OnlineBankingSlovakia, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "payment_method_data.bank_redirect.open_banking_slovakia.issuer".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.open_banking_slovakia.issuer".to_string(), - display_name: "issuer".to_string(), - field_type: enums::FieldType::UserBank, - value: None, - } - ), - ]), - common: HashMap::new(), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::OnlineBankingFpx, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "payment_method_data.bank_redirect.open_banking_fpx.issuer".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.open_banking_fpx.issuer".to_string(), - display_name: "issuer".to_string(), - field_type: enums::FieldType::UserBank, - value: None, - } - ) - ]), - common: HashMap::new(), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::OnlineBankingThailand, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "payment_method_data.bank_redirect.open_banking_thailand.issuer".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.open_banking_thailand.issuer".to_string(), - display_name: "issuer".to_string(), - field_type: enums::FieldType::UserBank, - value: None, - } - ) - ]), - common: HashMap::new(), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::Bizum, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::Przelewy24, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Stripe, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ) - ]), - common: HashMap::new(), - } - )]), - }, - ), - ( - enums::PaymentMethodType::BancontactCard, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Mollie, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Stripe, - RequiredFieldFinal { - mandate: HashMap::from([ - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ) - ]), - non_mandate: HashMap::new(), - common: HashMap::from([ - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "billing_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, - } - ), - ]), - } - ), - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from([ - ( - "payment_method_data.bank_redirect.bancontact_card.card_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.bancontact_card.card_number".to_string(), - display_name: "card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.bank_redirect.bancontact_card.card_exp_month".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.bancontact_card.card_exp_month".to_string(), - display_name: "card_exp_month".to_string(), - field_type: enums::FieldType::UserCardExpiryMonth, - value: None, - } - ), - ( - "payment_method_data.bank_redirect.bancontact_card.card_exp_year".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.bancontact_card.card_exp_year".to_string(), - display_name: "card_exp_year".to_string(), - field_type: enums::FieldType::UserCardExpiryYear, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ]), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::Giropay, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Aci, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "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![ - "DE".to_string(), - ]}, - value: None, - } - ) - ]), - common: HashMap::new(), - } - ), - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Globalpay, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from([ - ("billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry { - options: vec![ - "DE".to_string(), - ] - }, - value: None, - } - ) - ]), - } - ), - ( - enums::Connector::Mollie, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Nuvei, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate:HashMap::from([ - ( - "email".to_string(), - RequiredFieldInfo { - required_field: "email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "billing_first_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "billing_last_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "DE".to_string(), - ] - }, - value: None, - } - )] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Paypal, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "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![ - "DE".to_string(), - ] - }, - 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(), - } - ), - ( - enums::Connector::Stripe, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "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(), - } - ), - ( - enums::Connector::Shift4, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Trustpay, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "billing_first_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry { - options: vec![ - "DE".to_string(), - ] - }, - value: None, - } - ), - ]), - common: HashMap::new(), - } - ), - ]), - }, - ), - ( - enums::PaymentMethodType::Ideal, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Aci, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "payment_method_data.bank_redirect.ideal.bank_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.ideal.bank_name".to_string(), - display_name: "bank_name".to_string(), - field_type: enums::FieldType::UserBank, - 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![ - "NL".to_string(), - ] - }, - value: None, - } - ) - ]), - common: HashMap::new(), - } - ), - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from([ - ( - "payment_method_data.bank_redirect.ideal.bank_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.ideal.bank_name".to_string(), - display_name: "bank_name".to_string(), - field_type: enums::FieldType::UserBank, - value: None, - } - ), - - ]), - } - ), - ( - enums::Connector::Globalpay, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Mollie, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Nexinets, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Nuvei, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "email".to_string(), - RequiredFieldInfo { - required_field: "email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "billing_first_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "billing_last_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "NL".to_string(), - ] - }, - value: None, - } - ) - ]), - common: HashMap::new(), - } - ), - ( - enums::Connector::Shift4, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "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![ - "NL".to_string(), - ] - }, - value: None, - } - ) - ]), - common: HashMap::new(), - } - ), - ( - enums::Connector::Paypal, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "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, - } - ), - ( - "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![ - "NL".to_string(), - ] - }, - value: None, - } - ) - ]), - common: HashMap::new(), - } - ), - ( - enums::Connector::Stripe, - RequiredFieldFinal { - mandate: HashMap::from([ - ( - "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.billing.address.last_name".to_string(), - display_name: "billing_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "billing_email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ) - ]), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Trustpay, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "billing_first_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "NL".to_string(), - ] - }, - value: None, - } - ), - ]), - common: HashMap::new(), - } - ), - ]), - }, - ), - ( - enums::PaymentMethodType::Sofort, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Aci, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ("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![ - "ES".to_string(), - "GB".to_string(), - "SE".to_string(), - "AT".to_string(), - "NL".to_string(), - "DE".to_string(), - "CH".to_string(), - "BE".to_string(), - "FR".to_string(), - "FI".to_string(), - "IT".to_string(), - "PL".to_string(), - ] - }, - value: None, - } - ) - ]), - common: HashMap::new(), - } - ), - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Globalpay, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from([ - ("billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry { - options: vec![ - "AT".to_string(), - "BE".to_string(), - "DE".to_string(), - "ES".to_string(), - "IT".to_string(), - "NL".to_string(), - ] - }, - value: None, - } - ) - ]), - } - ), - ( - enums::Connector::Mollie, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Nexinets, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Nuvei, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate:HashMap::from([ - ( - "email".to_string(), - RequiredFieldInfo { - required_field: "email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "billing_first_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "billing_last_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "ES".to_string(), - "GB".to_string(), - "IT".to_string(), - "DE".to_string(), - "FR".to_string(), - "AT".to_string(), - "BE".to_string(), - "NL".to_string(), - "BE".to_string(), - "SK".to_string(), - ] - }, - value: None, - } - )] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Paypal, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( "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![ - "ES".to_string(), - "GB".to_string(), - "AT".to_string(), - "NL".to_string(), - "DE".to_string(), - "BE".to_string(), - ] - }, - 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(), - } - ), - ( - enums::Connector::Shift4, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Stripe, - RequiredFieldFinal { - mandate: HashMap::from([ - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ) - ]), - non_mandate : HashMap::new(), - common: HashMap::from([ - ("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![ - "ES".to_string(), - "AT".to_string(), - "NL".to_string(), - "DE".to_string(), - "BE".to_string(), - ] - }, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "account_holder_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "account_holder_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - )]), - } - ), - ( - enums::Connector::Trustpay, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "billing_first_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry { - options: vec![ - "ES".to_string(), - "GB".to_string(), - "SE".to_string(), - "AT".to_string(), - "NL".to_string(), - "DE".to_string(), - "CH".to_string(), - "BE".to_string(), - "FR".to_string(), - "FI".to_string(), - "IT".to_string(), - "PL".to_string(), - ] - }, - value: None, - } - ), - ]), - common: HashMap::new(), - } - ), - ]), - }, - ), - ( - enums::PaymentMethodType::Eps, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from([ - ( - "payment_method_data.bank_redirect.eps.bank_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.eps.bank_name".to_string(), - display_name: "bank_name".to_string(), - field_type: enums::FieldType::UserBank, - value: None, - } - ) - ]), - } - ), - ( - enums::Connector::Stripe, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "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.billing.address.last_name".to_string(), - display_name: "billing_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ]), - common: HashMap::new(), - } - ), - ( - enums::Connector::Aci, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "bank_account_country".to_string(), - field_type: enums::FieldType::UserCountry { - options: vec![ - "AT".to_string(), - ] - }, - value: None, - } - ) - ]), - common: HashMap::new(), - } - ), - ( - enums::Connector::Globalpay, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from([ - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry { - options: vec![ - "AT".to_string(), - ] - }, - value: None, - } - ) - ]) - } - ), - ( - enums::Connector::Mollie, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate:HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Paypal, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "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.billing.address.last_name".to_string(), - display_name: "billing_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "bank_account_country".to_string(), - field_type: enums::FieldType::UserCountry { - options: vec![ - "AT".to_string(), - ] - }, - value: None, - } - ) - ]), - common: HashMap::new(), - } - ), - ( - enums::Connector::Trustpay, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "billing_first_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "AT".to_string(), - ] - }, - value: None, - } - ), - ]), - common: HashMap::new(), - } - ), - ( - enums::Connector::Shift4, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate:HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Nuvei, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "email".to_string(), - RequiredFieldInfo { - required_field: "email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "billing_first_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "billing_last_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "AT".to_string(), - ] - }, - value: None, - } - )] - ), - common: HashMap::new(), - } - ), - ]), - }, - ), - ( - enums::PaymentMethodType::Blik, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from([ - ( - "payment_method_data.bank_redirect.blik.blik_code".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.blik.blik_code".to_string(), - display_name: "blik_code".to_string(), - field_type: enums::FieldType::UserBlikCode, - value: None, - } - ) - ]), - } - ), - ( - enums::Connector::Stripe, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from([ - ( - "payment_method_data.bank_redirect.blik.blik_code".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_redirect.blik.blik_code".to_string(), - display_name: "blik_code".to_string(), - field_type: enums::FieldType::UserBlikCode, - value: None, - } - ) - ]), - } - ), - ( - enums::Connector::Trustpay, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from([ - ( - "email".to_string(), - RequiredFieldInfo { - required_field: "email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "billing_first_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "billing_last_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ), - ]), - } - ) - ]), - }, - ), - ])), - ), - ( - enums::PaymentMethod::Wallet, - PaymentMethodType(HashMap::from([ - ( - enums::PaymentMethodType::ApplePay, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Stripe, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Bankofamerica, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "email".to_string(), - RequiredFieldInfo { - required_field: "email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "billing_first_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "billing_last_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.state".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.state".to_string(), - display_name: "state".to_string(), - field_type: enums::FieldType::UserAddressState, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Cybersource, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "billing_first_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "billing_last_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.state".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.state".to_string(), - display_name: "state".to_string(), - field_type: enums::FieldType::UserAddressState, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Wellsfargo, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "email".to_string(), - RequiredFieldInfo { - required_field: "email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "billing_first_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "billing_last_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.state".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.state".to_string(), - display_name: "state".to_string(), - field_type: enums::FieldType::UserAddressState, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "shipping.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.first_name".to_string(), - display_name: "shipping_first_name".to_string(), - field_type: enums::FieldType::UserShippingName, - value: None, - } - ), - ( - "shipping.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.last_name".to_string(), - display_name: "shipping_last_name".to_string(), - field_type: enums::FieldType::UserShippingName, - value: None, - } - ), - ( - "shipping.address.city".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserShippingAddressCity, - value: None, - } - ), - ( - "shipping.address.state".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.state".to_string(), - display_name: "state".to_string(), - field_type: enums::FieldType::UserShippingAddressState, - value: None, - } - ), - ( - "shipping.address.zip".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserShippingAddressPincode, - value: None, - } - ), - ( - "shipping.address.country".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserShippingAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ), - ( - "shipping.address.line1".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserShippingAddressLine1, - value: None, - } - ), - ] - ), - common: HashMap::new(), - } - ), - - ]), - }, - ), - ( - enums::PaymentMethodType::GooglePay, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Bankofamerica, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "email".to_string(), - RequiredFieldInfo { - required_field: "email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "billing_first_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "billing_last_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.state".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.state".to_string(), - display_name: "state".to_string(), - field_type: enums::FieldType::UserAddressState, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Bluesnap, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Noon, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Nuvei, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Airwallex, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Authorizedotnet, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Checkout, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Globalpay, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Multisafepay, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "billing_first_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "billing_last_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.state".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.state".to_string(), - display_name: "state".to_string(), - field_type: enums::FieldType::UserAddressState, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "billing.address.line2".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line2".to_string(), - display_name: "line2".to_string(), - field_type: enums::FieldType::UserAddressLine2, - value: None, - } - )]), - common: HashMap::new(), - } - ), - ( - enums::Connector::Cybersource, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "billing_first_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "billing_last_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.state".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.state".to_string(), - display_name: "state".to_string(), - field_type: enums::FieldType::UserAddressState, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ( - enums::Connector::Payu, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Rapyd, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Stripe, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Trustpay, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Wellsfargo, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "email".to_string(), - RequiredFieldInfo { - required_field: "email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "billing_first_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "billing_last_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.state".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.state".to_string(), - display_name: "state".to_string(), - field_type: enums::FieldType::UserAddressState, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "shipping.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.first_name".to_string(), - display_name: "shipping_first_name".to_string(), - field_type: enums::FieldType::UserShippingName, - value: None, - } - ), - ( - "shipping.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.last_name".to_string(), - display_name: "shipping_last_name".to_string(), - field_type: enums::FieldType::UserShippingName, - value: None, - } - ), - ( - "shipping.address.city".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserShippingAddressCity, - value: None, - } - ), - ( - "shipping.address.state".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.state".to_string(), - display_name: "state".to_string(), - field_type: enums::FieldType::UserShippingAddressState, - value: None, - } - ), - ( - "shipping.address.zip".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserShippingAddressPincode, - value: None, - } - ), - ( - "shipping.address.country".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserShippingAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ), - ( - "shipping.address.line1".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserShippingAddressLine1, - value: None, - } - ), - ] - ), - common: HashMap::new(), - } - ), - ]), - }, - ), - ( - enums::PaymentMethodType::WeChatPay, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Stripe, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ]), - }, - ), - ( - enums::PaymentMethodType::AliPay, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Stripe, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ]), - }, - ), - ( - enums::PaymentMethodType::AliPayHk, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ]), - }, - ), - ( - enums::PaymentMethodType::Cashapp, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Stripe, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::MbWay, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - common: HashMap::new(), - non_mandate: HashMap::from([ - ( - "payment_method_data.billing.phone.number".to_string(), - RequiredFieldInfo { - required_field: "billing.phone.number".to_string(), - display_name: "phone_number".to_string(), - field_type: enums::FieldType::UserPhoneNumber, - value: None, - } - ), - ( - "billing.phone.country_code".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.phone.country_code".to_string(), - display_name: "dialing_code".to_string(), - field_type: enums::FieldType::UserPhoneNumberCountryCode, - value: None, - } - ), - ] - ), - } - ), - ]), - }, - ), - ( - enums::PaymentMethodType::KakaoPay, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ]), - }, - ), - ( - enums::PaymentMethodType::Twint, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ]), - }, - ), - ( - enums::PaymentMethodType::Gcash, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ]), - }, - ), - ( - enums::PaymentMethodType::Vipps, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ]), - }, - ), - ( - enums::PaymentMethodType::Dana, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ]), - }, - ), - ( - enums::PaymentMethodType::Momo, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ]), - }, - ), - ( - enums::PaymentMethodType::Swish, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ]), - }, - ), - ( - enums::PaymentMethodType::TouchNGo, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ]), - }, - ), - ( - // Added shipping fields for the SDK flow to accept it from wallet directly, - // this won't show up in SDK in payment's sheet but will be used in the background - enums::PaymentMethodType::Paypal, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from([ - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - )] - ), - } - ), - ( - enums::Connector::Braintree, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Paypal, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ]), - }, - ), - ( - enums::PaymentMethodType::Mifinity, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Mifinity, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from([ - ( - "payment_method_data.wallet.mifinity.date_of_birth".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.wallet.mifinity.date_of_birth".to_string(), - display_name: "date_of_birth".to_string(), - field_type: enums::FieldType::UserDateOfBirth, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "first_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "last_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.phone.number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.phone.number".to_string(), - display_name: "phone".to_string(), - field_type: enums::FieldType::UserPhoneNumber, - value: None, - } - ), - ( - "billing.phone.country_code".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.phone.country_code".to_string(), - display_name: "dialing_code".to_string(), - field_type: enums::FieldType::UserPhoneNumberCountryCode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "nationality".to_string(), - field_type: enums::FieldType::UserCountry{ - options: vec![ - "BR".to_string(), - "CN".to_string(), - "SG".to_string(), - "MY".to_string(), - "DE".to_string(), - "CH".to_string(), - "DK".to_string(), - "GB".to_string(), - "ES".to_string(), - "AD".to_string(), - "GI".to_string(), - "FI".to_string(), - "FR".to_string(), - "GR".to_string(), - "HR".to_string(), - "IT".to_string(), - "JP".to_string(), - "MX".to_string(), - "AR".to_string(), - "CO".to_string(), - "CL".to_string(), - "PE".to_string(), - "VE".to_string(), - "UY".to_string(), - "PY".to_string(), - "BO".to_string(), - "EC".to_string(), - "GT".to_string(), - "HN".to_string(), - "SV".to_string(), - "NI".to_string(), - "CR".to_string(), - "PA".to_string(), - "DO".to_string(), - "CU".to_string(), - "PR".to_string(), - "NL".to_string(), - "NO".to_string(), - "PL".to_string(), - "PT".to_string(), - "SE".to_string(), - "RU".to_string(), - "TR".to_string(), - "TW".to_string(), - "HK".to_string(), - "MO".to_string(), - "AX".to_string(), - "AL".to_string(), - "DZ".to_string(), - "AS".to_string(), - "AO".to_string(), - "AI".to_string(), - "AG".to_string(), - "AM".to_string(), - "AW".to_string(), - "AU".to_string(), - "AT".to_string(), - "AZ".to_string(), - "BS".to_string(), - "BH".to_string(), - "BD".to_string(), - "BB".to_string(), - "BE".to_string(), - "BZ".to_string(), - "BJ".to_string(), - "BM".to_string(), - "BT".to_string(), - "BQ".to_string(), - "BA".to_string(), - "BW".to_string(), - "IO".to_string(), - "BN".to_string(), - "BG".to_string(), - "BF".to_string(), - "BI".to_string(), - "KH".to_string(), - "CM".to_string(), - "CA".to_string(), - "CV".to_string(), - "KY".to_string(), - "CF".to_string(), - "TD".to_string(), - "CX".to_string(), - "CC".to_string(), - "KM".to_string(), - "CG".to_string(), - "CK".to_string(), - "CI".to_string(), - "CW".to_string(), - "CY".to_string(), - "CZ".to_string(), - "DJ".to_string(), - "DM".to_string(), - "EG".to_string(), - "GQ".to_string(), - "ER".to_string(), - "EE".to_string(), - "ET".to_string(), - "FK".to_string(), - "FO".to_string(), - "FJ".to_string(), - "GF".to_string(), - "PF".to_string(), - "TF".to_string(), - "GA".to_string(), - "GM".to_string(), - "GE".to_string(), - "GH".to_string(), - "GL".to_string(), - "GD".to_string(), - "GP".to_string(), - "GU".to_string(), - "GG".to_string(), - "GN".to_string(), - "GW".to_string(), - "GY".to_string(), - "HT".to_string(), - "HM".to_string(), - "VA".to_string(), - "IS".to_string(), - "IN".to_string(), - "ID".to_string(), - "IE".to_string(), - "IM".to_string(), - "IL".to_string(), - "JE".to_string(), - "JO".to_string(), - "KZ".to_string(), - "KE".to_string(), - "KI".to_string(), - "KW".to_string(), - "KG".to_string(), - "LA".to_string(), - "LV".to_string(), - "LB".to_string(), - "LS".to_string(), - "LI".to_string(), - "LT".to_string(), - "LU".to_string(), - "MK".to_string(), - "MG".to_string(), - "MW".to_string(), - "MV".to_string(), - "ML".to_string(), - "MT".to_string(), - "MH".to_string(), - "MQ".to_string(), - "MR".to_string(), - "MU".to_string(), - "YT".to_string(), - "FM".to_string(), - "MD".to_string(), - "MC".to_string(), - "MN".to_string(), - "ME".to_string(), - "MS".to_string(), - "MA".to_string(), - "MZ".to_string(), - "NA".to_string(), - "NR".to_string(), - "NP".to_string(), - "NC".to_string(), - "NZ".to_string(), - "NE".to_string(), - "NG".to_string(), - "NU".to_string(), - "NF".to_string(), - "MP".to_string(), - "OM".to_string(), - "PK".to_string(), - "PW".to_string(), - "PS".to_string(), - "PG".to_string(), - "PH".to_string(), - "PN".to_string(), - "QA".to_string(), - "RE".to_string(), - "RO".to_string(), - "RW".to_string(), - "BL".to_string(), - "SH".to_string(), - "KN".to_string(), - "LC".to_string(), - "MF".to_string(), - "PM".to_string(), - "VC".to_string(), - "WS".to_string(), - "SM".to_string(), - "ST".to_string(), - "SA".to_string(), - "SN".to_string(), - "RS".to_string(), - "SC".to_string(), - "SL".to_string(), - "SX".to_string(), - "SK".to_string(), - "SI".to_string(), - "SB".to_string(), - "SO".to_string(), - "ZA".to_string(), - "GS".to_string(), - "KR".to_string(), - "LK".to_string(), - "SR".to_string(), - "SJ".to_string(), - "SZ".to_string(), - "TH".to_string(), - "TL".to_string(), - "TG".to_string(), - "TK".to_string(), - "TO".to_string(), - "TT".to_string(), - "TN".to_string(), - "TM".to_string(), - "TC".to_string(), - "TV".to_string(), - "UG".to_string(), - "UA".to_string(), - "AE".to_string(), - "UZ".to_string(), - "VU".to_string(), - "VN".to_string(), - "VG".to_string(), - "VI".to_string(), - "WF".to_string(), - "EH".to_string(), - "ZM".to_string(), - ] - }, - value: None, - } - - ), - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email_address".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "payment_method_data.wallet.mifinity.language_preference".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.wallet.mifinity.language_preference".to_string(), - display_name: "language_preference".to_string(), - field_type: enums::FieldType::LanguagePreference{ - options: vec![ - "BR".to_string(), - "PT_BR".to_string(), - "CN".to_string(), - "ZH_CN".to_string(), - "DE".to_string(), - "DK".to_string(), - "DA".to_string(), - "DA_DK".to_string(), - "EN".to_string(), - "ES".to_string(), - "FI".to_string(), - "FR".to_string(), - "GR".to_string(), - "EL".to_string(), - "EL_GR".to_string(), - "HR".to_string(), - "IT".to_string(), - "JP".to_string(), - "JA".to_string(), - "JA_JP".to_string(), - "LA".to_string(), - "ES_LA".to_string(), - "NL".to_string(), - "NO".to_string(), - "PL".to_string(), - "PT".to_string(), - "RU".to_string(), - "SV".to_string(), - "SE".to_string(), - "SV_SE".to_string(), - "ZH".to_string(), - "TW".to_string(), - "ZH_TW".to_string(), - ] - }, - value: None, - } - ), - ]), - } - ), - ]), - } - ), - ])), - ), - ( - enums::PaymentMethod::PayLater, - PaymentMethodType(HashMap::from([ - ( - enums::PaymentMethodType::AfterpayClearpay, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Stripe, - RequiredFieldFinal { - mandate : HashMap::new(), - non_mandate: HashMap::from([ - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "billing_first_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "billing_last_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "GB".to_string(), - "AU".to_string(), - "CA".to_string(), - "US".to_string(), - "NZ".to_string(), - ] - }, - value: None, - } - ), - ( - "billing.address.state".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.state".to_string(), - display_name: "state".to_string(), - field_type: enums::FieldType::UserAddressState, - value: None, - } - ), - ( - "shipping.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.first_name".to_string(), - display_name: "shipping_first_name".to_string(), - field_type: enums::FieldType::UserShippingName, - value: None, - } - ), - ( - "shipping.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.last_name".to_string(), - display_name: "shipping_last_name".to_string(), - field_type: enums::FieldType::UserShippingName, - value: None, - } - ), - ( - "shipping.address.city".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserShippingAddressCity, - value: None, - } - ), - ( - "shipping.address.state".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.state".to_string(), - display_name: "state".to_string(), - field_type: enums::FieldType::UserShippingAddressState, - value: None, - } - ), - ( - "shipping.address.zip".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserShippingAddressPincode, - value: None, - } - ), - ( - "shipping.address.country".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserShippingAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ), - ( - "shipping.address.line1".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserShippingAddressLine1, - value: None, - } - ), - ]), - common : HashMap::new(), - } - ), - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate : HashMap::new(), - non_mandate: HashMap::from([ - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "billing_first_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "billing_last_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "billing.address.line2".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line2".to_string(), - display_name: "line2".to_string(), - field_type: enums::FieldType::UserAddressLine2, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "GB".to_string(), - "AU".to_string(), - "CA".to_string(), - "US".to_string(), - "NZ".to_string(), - ] - }, - value: None, - } - ), - ( - "billing.address.state".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.state".to_string(), - display_name: "state".to_string(), - field_type: enums::FieldType::UserAddressState, - value: None, - } - ), - ( - "shipping.address.city".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserShippingAddressCity, - value: None, - } - ), - ( - "shipping.address.zip".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserShippingAddressPincode, - value: None, - } - ), - ( - "shipping.address.country".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserShippingAddressCountry{ - options: vec![ - "GB".to_string(), - "AU".to_string(), - "CA".to_string(), - "US".to_string(), - "NZ".to_string(), - ] - }, - value: None, - } - ), - ( - "shipping.address.line1".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserShippingAddressLine1, - value: None, - } - ), - ( - "shipping.address.line2".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.line2".to_string(), - display_name: "line2".to_string(), - field_type: enums::FieldType::UserShippingAddressLine2, - value: None, - } - ), - ]), - common : HashMap::new(), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::Klarna, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Stripe, - RequiredFieldFinal { - mandate : HashMap::new(), - non_mandate: HashMap::from([ - ( "payment_method_data.pay_later.klarna.billing_country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.pay_later.klarna.billing_country".to_string(), - display_name: "billing_country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "AU".to_string(), - "AT".to_string(), - "BE".to_string(), - "CA".to_string(), - "CZ".to_string(), - "DK".to_string(), - "FI".to_string(), - "FR".to_string(), - "GR".to_string(), - "DE".to_string(), - "IE".to_string(), - "IT".to_string(), - "NL".to_string(), - "NZ".to_string(), - "NO".to_string(), - "PL".to_string(), - "PT".to_string(), - "RO".to_string(), - "ES".to_string(), - "SE".to_string(), - "CH".to_string(), - "GB".to_string(), - "US".to_string(), - ] - }, - value: None, - }), - ("billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - }) - ]), - common : HashMap::new(), - } - ), - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate : HashMap::new(), - non_mandate: HashMap::new(), - common : HashMap::from([ - ( "payment_method_data.pay_later.klarna.billing_country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.pay_later.klarna.billing_country".to_string(), - display_name: "billing_country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - }), - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ]), - } - ), - ( - enums::Connector::Klarna, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from([ - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "AU".to_string(), - "AT".to_string(), - "BE".to_string(), - "CA".to_string(), - "CZ".to_string(), - "DK".to_string(), - "FI".to_string(), - "FR".to_string(), - "DE".to_string(), - "GR".to_string(), - "IE".to_string(), - "IT".to_string(), - "NL".to_string(), - "NZ".to_string(), - "NO".to_string(), - "PL".to_string(), - "PT".to_string(), - "ES".to_string(), - "SE".to_string(), - "CH".to_string(), - "GB".to_string(), - "US".to_string(), - ] - }, - value: None, - } - ) - ]), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::Affirm, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Stripe, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.state".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.state".to_string(), - display_name: "state".to_string(), - field_type: enums::FieldType::UserAddressState, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "US".to_string(), - ] - }, - value: None, - } - ), - ( - "billing.phone.number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.phone.number".to_string(), - display_name: "phone_number".to_string(), - field_type: enums::FieldType::UserPhoneNumber, - value: None, - } - ), - ( - "billing.phone.country_code".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.phone.country_code".to_string(), - display_name: "dialing_code".to_string(), - field_type: enums::FieldType::UserPhoneNumberCountryCode, - value: None, - } - ), - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "billing.address.line2".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line2".to_string(), - display_name: "line2".to_string(), - field_type: enums::FieldType::UserAddressLine2, - value: None, - } - ), - ( - "shipping.address.line1".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "shipping.address.line2".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.line2".to_string(), - display_name: "line2".to_string(), - field_type: enums::FieldType::UserAddressLine2, - value: None, - } - ), - ( - "shipping.address.zip".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserShippingAddressPincode, - value: None, - } - ), - ( - "shipping.address.city".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserShippingAddressCity, - value: None, - } - ), - ( - "shipping.address.country".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserCountry { - options: vec![ - "US".to_string(), - ]}, - value: None, - } - ), - - ] - ), - common: HashMap::new(), - } - ), - ]), - }, - ), - ( - enums::PaymentMethodType::PayBright, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.state".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.state".to_string(), - display_name: "state".to_string(), - field_type: enums::FieldType::UserAddressState, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "CA".to_string(), - ] - }, - value: None, - } - ), - ( - "payment_method_data.billing.phone.number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.phone.number".to_string(), - display_name: "phone_number".to_string(), - field_type: enums::FieldType::UserPhoneNumber, - value: None, - } - ), - ( - "billing.phone.country_code".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.phone.country_code".to_string(), - display_name: "dialing_code".to_string(), - field_type: enums::FieldType::UserPhoneNumberCountryCode, - value: None, - } - ), - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "billing.address.line2".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line2".to_string(), - display_name: "line2".to_string(), - field_type: enums::FieldType::UserAddressLine2, - value: None, - } - ), - ( - "shipping.address.city".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserShippingAddressCity, - value: None, - } - ), - ( - "shipping.address.zip".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserShippingAddressPincode, - value: None, - } - ), - ( - "shipping.address.country".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserShippingAddressCountry{ - options: vec![ - "ALL".to_string(), - ] - }, - value: None, - } - ), - ( - "shipping.address.line1".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserShippingAddressLine1, - value: None, - } - ), - ( - "shipping.address.line2".to_string(), - RequiredFieldInfo { - required_field: "shipping.address.line2".to_string(), - display_name: "line2".to_string(), - field_type: enums::FieldType::UserShippingAddressLine2, - value: None, - } - ), - ] - ), - common: HashMap::new(), - } - ), - ]), - }, - ), - ( - enums::PaymentMethodType::Walley, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "billing.phone.number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.phone.number".to_string(), - display_name: "phone".to_string(), - field_type: enums::FieldType::UserPhoneNumber, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "DK".to_string(), - "FI".to_string(), - "NO".to_string(), - "SE".to_string(), - ]}, - value: None, - } - ), - ( - "billing.phone.country_code".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.phone.country_code".to_string(), - display_name: "dialing_code".to_string(), - field_type: enums::FieldType::UserPhoneNumberCountryCode, - value: None, - } - ), - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ] - ), - common: HashMap::new(), - } - ), - ]), - }, - ), - ( - enums::PaymentMethodType::Alma, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.state".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.state".to_string(), - display_name: "state".to_string(), - field_type: enums::FieldType::UserAddressState, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "FR".to_string(), - ] - }, - value: None, - } - ), - ( - "payment_method_data.billing.phone.number".to_string(), - RequiredFieldInfo { - required_field: "billing.phone.number".to_string(), - display_name: "phone_number".to_string(), - field_type: enums::FieldType::UserPhoneNumber, - value: None, - } - ), - ( - "billing.phone.country_code".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.phone.country_code".to_string(), - display_name: "dialing_code".to_string(), - field_type: enums::FieldType::UserPhoneNumberCountryCode, - value: None, - } - ), - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "billing.address.line2".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line2".to_string(), - display_name: "line2".to_string(), - field_type: enums::FieldType::UserAddressLine2, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ]), - }, - ), - ( - enums::PaymentMethodType::Atome, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [ - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.state".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.state".to_string(), - display_name: "state".to_string(), - field_type: enums::FieldType::UserAddressState, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "MY".to_string(), - "SG".to_string() - ] - }, - value: None, - } - ), - ( - "payment_method_data.billing.phone.number".to_string(), - RequiredFieldInfo { - required_field: "billing.phone.number".to_string(), - display_name: "phone_number".to_string(), - field_type: enums::FieldType::UserPhoneNumber, - value: None, - } - ), - ( - "billing.phone.country_code".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.phone.country_code".to_string(), - display_name: "dialing_code".to_string(), - field_type: enums::FieldType::UserPhoneNumberCountryCode, - value: None, - } - ), - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "billing.address.line2".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line2".to_string(), - display_name: "line2".to_string(), - field_type: enums::FieldType::UserAddressLine2, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ]), - }, - ), - ])), - ), - ( - enums::PaymentMethod::Crypto, - PaymentMethodType(HashMap::from([ - ( - enums::PaymentMethodType::CryptoCurrency, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Cryptopay, - RequiredFieldFinal { - mandate : HashMap::new(), - non_mandate: HashMap::from([ - ( - "payment_method_data.crypto.pay_currency".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.crypto.pay_currency".to_string(), - display_name: "currency".to_string(), - field_type: enums::FieldType::UserCurrency{ - options: vec![ - "BTC".to_string(), - "LTC".to_string(), - "ETH".to_string(), - "XRP".to_string(), - "XLM".to_string(), - "BCH".to_string(), - "ADA".to_string(), - "SOL".to_string(), - "SHIB".to_string(), - "TRX".to_string(), - "DOGE".to_string(), - "BNB".to_string(), - "USDT".to_string(), - "USDC".to_string(), - "DAI".to_string(), - ] - }, - value: None, - } - ), - ( - "payment_method_data.crypto.network".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.crypto.network".to_string(), - display_name: "network".to_string(), - field_type: enums::FieldType::UserCryptoCurrencyNetwork, - value: None, - } - ), - ]), - common : HashMap::new(), - } - ), - ]), - }, - ), - ])), - ), - ( - enums::PaymentMethod::Voucher, - PaymentMethodType(HashMap::from([ - ( - enums::PaymentMethodType::Boleto, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate : HashMap::new(), - non_mandate : HashMap::from([ - ( - "payment_method_data.voucher.boleto.social_security_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.voucher.boleto.social_security_number".to_string(), - display_name: "social_security_number".to_string(), - field_type: enums::FieldType::Text, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - } - ), - ( - "billing.address.state".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.state".to_string(), - display_name: "state".to_string(), - field_type: enums::FieldType::UserAddressState, - value: None, - } - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "BR".to_string(), - ] - }, - value: None, - } - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - } - ), - ( - "billing.address.line2".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line2".to_string(), - display_name: "line2".to_string(), - field_type: enums::FieldType::UserAddressLine2, - value: None, - } - ), - ]), - common : HashMap::new(), - } - ), - ( - enums::Connector::Zen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::Alfamart, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate : HashMap::new(), - non_mandate : HashMap::from([ - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ) - ]), - common : HashMap::new(), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::Indomaret, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate : HashMap::new(), - non_mandate : HashMap::from([ - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ) - ]), - common : HashMap::new(), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::Oxxo, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate : HashMap::new(), - non_mandate : HashMap::new(), - common : HashMap::new(), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::SevenEleven, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate : HashMap::new(), - non_mandate : HashMap::from([ - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.phone.number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.phone.number".to_string(), - display_name: "phone".to_string(), - field_type: enums::FieldType::UserPhoneNumber, - value: None, - } - ), - ( - "billing.phone.country_code".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.phone.country_code".to_string(), - display_name: "dialing_code".to_string(), - field_type: enums::FieldType::UserPhoneNumberCountryCode, - value: None, - } - ) - ] - ), - common : HashMap::new(), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::Lawson, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate : HashMap::new(), - non_mandate : HashMap::from([ - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.phone.number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.phone.number".to_string(), - display_name: "phone".to_string(), - field_type: enums::FieldType::UserPhoneNumber, - value: None, - } - ), - ( - "billing.phone.country_code".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.phone.country_code".to_string(), - display_name: "dialing_code".to_string(), - field_type: enums::FieldType::UserPhoneNumberCountryCode, - value: None, - } - ), - ] - ), - common : HashMap::new(), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::MiniStop, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate : HashMap::new(), - non_mandate : HashMap::from([ - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.phone.number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.phone.number".to_string(), - display_name: "phone".to_string(), - field_type: enums::FieldType::UserPhoneNumber, - value: None, - } - ), - ( - "billing.phone.country_code".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.phone.country_code".to_string(), - display_name: "dialing_code".to_string(), - field_type: enums::FieldType::UserPhoneNumberCountryCode, - value: None, - } - ), - ] - ), - common : HashMap::new(), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::FamilyMart, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate : HashMap::new(), - non_mandate : HashMap::from([ - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.phone.number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.phone.number".to_string(), - display_name: "phone".to_string(), - field_type: enums::FieldType::UserPhoneNumber, - value: None, - } - ), - ( - "billing.phone.country_code".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.phone.country_code".to_string(), - display_name: "dialing_code".to_string(), - field_type: enums::FieldType::UserPhoneNumberCountryCode, - value: None, - } - ), - ] - ), - common : HashMap::new(), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::Seicomart, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate : HashMap::new(), - non_mandate : HashMap::from([ - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.phone.number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.phone.number".to_string(), - display_name: "phone".to_string(), - field_type: enums::FieldType::UserPhoneNumber, - value: None, - } - ), - ( - "billing.phone.country_code".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.phone.country_code".to_string(), - display_name: "dialing_code".to_string(), - field_type: enums::FieldType::UserPhoneNumberCountryCode, - value: None, - } - ), - ] - ), - common : HashMap::new(), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::PayEasy, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate : HashMap::new(), - non_mandate : HashMap::from([ - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.phone.number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.phone.number".to_string(), - display_name: "phone".to_string(), - field_type: enums::FieldType::UserPhoneNumber, - value: None, - } - ), - ( - "billing.phone.country_code".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.phone.country_code".to_string(), - display_name: "dialing_code".to_string(), - field_type: enums::FieldType::UserPhoneNumberCountryCode, - value: None, - } - ), - ] - ), - common : HashMap::new(), - } - ) - ]), - }, - ), - ])), - ), - ( - enums::PaymentMethod::Upi, - PaymentMethodType(HashMap::from([ - ( - enums::PaymentMethodType::UpiCollect, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Razorpay, - RequiredFieldFinal { - mandate : HashMap::new(), - non_mandate : HashMap::new(), - common : HashMap::from([ - ( - "payment_method_data.upi.upi_collect.vpa_id".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.upi.upi_collect.vpa_id".to_string(), - display_name: "vpa_id".to_string(), - field_type: enums::FieldType::UserVpaId, - value: None, - } - ), - ]), - } - ), - ]), - }, - ), - ])), - ), - ( - enums::PaymentMethod::BankDebit, - PaymentMethodType(HashMap::from([( - enums::PaymentMethodType::Ach, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Stripe, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from([( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "billing_first_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "owner_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "payment_method_data.bank_debit.ach.account_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_debit.ach.account_number".to_string(), - display_name: "bank_account_number".to_string(), - field_type: enums::FieldType::UserBankAccountNumber, - value: None, - } - ), - ( - "payment_method_data.bank_debit.ach.routing_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_debit.ach.routing_number".to_string(), - display_name: "bank_routing_number".to_string(), - field_type: enums::FieldType::Text, - value: None, - } - ) - ]), - }), - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from([ ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "owner_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - }), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "owner_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "payment_method_data.bank_debit.ach.account_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_debit.ach.account_number".to_string(), - display_name: "bank_account_number".to_string(), - field_type: enums::FieldType::UserBankAccountNumber, - value: None, - } - ), - ( - "payment_method_data.bank_debit.ach.routing_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_debit.ach.routing_number".to_string(), - display_name: "bank_routing_number".to_string(), - field_type: enums::FieldType::Text, - value: None, - } - ) - ]), - }) - ] - )} - ), - ( - enums::PaymentMethodType::Sepa, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Stripe, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from([ ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "billing_first_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "owner_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "payment_method_data.bank_debit.sepa.iban".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_debit.sepa.iban".to_string(), - display_name: "iban".to_string(), - field_type: enums::FieldType::UserIban, - value: None, - } - ), - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ) - ]), - } - ), - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from([ ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "owner_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - }), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "owner_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "payment_method_data.bank_debit.sepa.iban".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_debit.sepa.iban".to_string(), - display_name: "iban".to_string(), - field_type: enums::FieldType::UserIban, - value: None, - } - ) - ]), - } - ), - ( - enums::Connector::Deutschebank, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from([ ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "owner_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - }), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "owner_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "payment_method_data.bank_debit.sepa.iban".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_debit.sepa.iban".to_string(), - display_name: "iban".to_string(), - field_type: enums::FieldType::UserIban, - value: None, - } - ) - ]), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::Bacs, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Stripe, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from([ ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "billing_first_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "payment_method_data.bank_debit.bacs.account_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_debit.bacs.account_number".to_string(), - display_name: "bank_account_number".to_string(), - field_type: enums::FieldType::UserBankAccountNumber, - value: None, - } - ), - ( - "payment_method_data.bank_debit.bacs.sort_code".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_debit.bacs.sort_code".to_string(), - display_name: "bank_sort_code".to_string(), - field_type: enums::FieldType::Text, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry { - options: vec!["UK".to_string()], - }, - value: None, - }, - ), - ( - "billing.address.zip".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.zip".to_string(), - display_name: "zip".to_string(), - field_type: enums::FieldType::UserAddressPincode, - value: None, - }, - ), - ( - "billing.address.line1".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.line1".to_string(), - display_name: "line1".to_string(), - field_type: enums::FieldType::UserAddressLine1, - value: None, - }, - ) - ]), - } - ), - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from([ ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "owner_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - }), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "owner_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "payment_method_data.bank_debit.bacs.account_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_debit.bacs.account_number".to_string(), - display_name: "bank_account_number".to_string(), - field_type: enums::FieldType::UserBankAccountNumber, - value: None, - } - ), - ( - "payment_method_data.bank_debit.bacs.sort_code".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_debit.bacs.sort_code".to_string(), - display_name: "bank_sort_code".to_string(), - field_type: enums::FieldType::Text, - value: None, - } - ) - ]), - }) - ]), - }, - ), - ( - enums::PaymentMethodType::Becs, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Stripe, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from([ ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "billing_first_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "owner_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "payment_method_data.bank_debit.becs.account_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_debit.becs.account_number".to_string(), - display_name: "bank_account_number".to_string(), - field_type: enums::FieldType::UserBankAccountNumber, - value: None, - } - ), - ( - "payment_method_data.bank_debit.becs.bsb_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_debit.becs.bsb_number".to_string(), - display_name: "bsb_number".to_string(), - field_type: enums::FieldType::Text, - value: None, - } - ), - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ) - ]), - } - ), - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from([ ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "owner_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - }), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "owner_name".to_string(), - field_type: enums::FieldType::UserBillingName, - value: None, - } - ), - ( - "payment_method_data.bank_debit.bacs.account_number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_debit.bacs.account_number".to_string(), - display_name: "bank_account_number".to_string(), - field_type: enums::FieldType::UserBankAccountNumber, - value: None, - } - ), - ( - "payment_method_data.bank_debit.bacs.sort_code".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_debit.bacs.sort_code".to_string(), - display_name: "bank_sort_code".to_string(), - field_type: enums::FieldType::Text, - value: None, - } - ) - ]), - }) - ]), - }, - ), - ]))), - ( - enums::PaymentMethod::BankTransfer, - PaymentMethodType(HashMap::from([( - enums::PaymentMethodType::Multibanco, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Stripe, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ) - ]), - common: HashMap::new(), - } - ), - ])}), - (enums::PaymentMethodType::LocalBankTransfer, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Zsl, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry{ - options: vec![ - "CN".to_string(), - ] - }, - value: None, - } - ), - ( - "billing.address.city".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.city".to_string(), - display_name: "city".to_string(), - field_type: enums::FieldType::UserAddressCity, - value: None, - }, - ), - ]), - common: HashMap::new(), - } - ), - ])}), - (enums::PaymentMethodType::Ach, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Stripe, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from([ - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ) - ]) - } - ), - ])}), - (enums::PaymentMethodType::Pix, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Itaubank, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::from( - [ - ( - "payment_method_data.bank_transfer.pix.pix_key".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_transfer.pix.pix_key".to_string(), - display_name: "pix_key".to_string(), - field_type: enums::FieldType::UserPixKey, - value: None, - } - ), - ( - "payment_method_data.bank_transfer.pix.cnpj".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_transfer.pix.cnpj".to_string(), - display_name: "cnpj".to_string(), - field_type: enums::FieldType::UserCnpj, - value: None, - } - ), - ( - "payment_method_data.bank_transfer.pix.cpf".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.bank_transfer.pix.cpf".to_string(), - display_name: "cpf".to_string(), - field_type: enums::FieldType::UserCpf, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ] - ), - } - ), - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ])}), - ( - enums::PaymentMethodType::PermataBankTransfer, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate : HashMap::new(), - non_mandate : HashMap::from([ - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ) - ]), - common : HashMap::new(), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::BcaBankTransfer, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate : HashMap::new(), - non_mandate : HashMap::from([ - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ) - ]), - common : HashMap::new(), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::BniVa, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate : HashMap::new(), - non_mandate : HashMap::from([ - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ) - ]), - common : HashMap::new(), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::BriVa, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate : HashMap::new(), - non_mandate : HashMap::from([ - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ) - ]), - common : HashMap::new(), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::CimbVa, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate : HashMap::new(), - non_mandate : HashMap::from([ - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ) - ]), - common : HashMap::new(), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::DanamonVa, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate : HashMap::new(), - non_mandate : HashMap::from([ - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ) - ]), - common : HashMap::new(), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::MandiriVa, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate : HashMap::new(), - non_mandate : HashMap::from([ - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ) - ]), - common : HashMap::new(), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::Sepa, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Stripe, - RequiredFieldFinal { - mandate : HashMap::new(), - non_mandate : HashMap::new(), - common : HashMap::from([ - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.country".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.country".to_string(), - display_name: "country".to_string(), - field_type: enums::FieldType::UserAddressCountry { - options: vec![ - "BE".to_string(), - "DE".to_string(), - "ES".to_string(), - "FR".to_string(), - "IE".to_string(), - "NL".to_string(), - ], - }, - value: None, - }, - ), - ]), - } - ) - ]), - }, - ), - ( - enums::PaymentMethodType::Bacs, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Stripe, - RequiredFieldFinal { - mandate : HashMap::new(), - non_mandate : HashMap::new(), - common : HashMap::from([ - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ), - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "card_holder_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ) - ]), - } - ) - ]), - }, - ), - ]))), - ( - enums::PaymentMethod::GiftCard, - PaymentMethodType(HashMap::from([ - ( - enums::PaymentMethodType::PaySafeCard, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ]), - }, - ), - ( - enums::PaymentMethodType::Givex, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from([ - - ( - "payment_method_data.gift_card.number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.gift_card.number".to_string(), - display_name: "gift_card_number".to_string(), - field_type: enums::FieldType::UserCardNumber, - value: None, - } - ), - ( - "payment_method_data.gift_card.cvc".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.gift_card.cvc".to_string(), - display_name: "gift_card_cvc".to_string(), - field_type: enums::FieldType::UserCardCvc, - value: None, - } - ), - ]), - common: HashMap::new(), - } - ), - ]), - }, - ), - ])) - ), - ( - enums::PaymentMethod::CardRedirect, - PaymentMethodType(HashMap::from([ - ( - enums::PaymentMethodType::Benefit, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "first_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "last_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.phone.number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.phone.number".to_string(), - display_name: "phone".to_string(), - field_type: enums::FieldType::UserPhoneNumber, - value: None, - } - ), - ( - "billing.phone.country_code".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.phone.country_code".to_string(), - display_name: "dialing_code".to_string(), - field_type: enums::FieldType::UserPhoneNumberCountryCode, - value: None, - } - ), - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ]), - }, - ), - ( - enums::PaymentMethodType::Knet, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::from( - [( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "first_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "last_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.phone.number".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.phone.number".to_string(), - display_name: "phone".to_string(), - field_type: enums::FieldType::UserPhoneNumber, - value: None, - } - ), - ( - "billing.phone.country_code".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.phone.country_code".to_string(), - display_name: "dialing_code".to_string(), - field_type: enums::FieldType::UserPhoneNumberCountryCode, - value: None, - } - ), - ( - "billing.email".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.email".to_string(), - display_name: "email".to_string(), - field_type: enums::FieldType::UserEmailAddress, - value: None, - } - ) - ] - ), - common: HashMap::new(), - } - ), - ]), - }, - ), - ( - enums::PaymentMethodType::MomoAtm, - ConnectorFields { - fields: HashMap::from([ - ( - enums::Connector::Adyen, - RequiredFieldFinal { - mandate: HashMap::new(), - non_mandate: HashMap::new(), - common: HashMap::new(), - } - ), - ]), - }, - ) - ])) - ) - ])) - } -} - #[allow(clippy::derivable_impls)] impl Default for super::settings::ApiKeys { fn default() -> Self { diff --git a/crates/router/src/configs/defaults/payment_connector_required_fields.rs b/crates/router/src/configs/defaults/payment_connector_required_fields.rs new file mode 100644 index 000000000000..9e42aec4a51d --- /dev/null +++ b/crates/router/src/configs/defaults/payment_connector_required_fields.rs @@ -0,0 +1,12998 @@ +use std::collections::{HashMap, HashSet}; + +use api_models::{enums, payment_methods::RequiredFieldInfo}; + +use crate::settings::{ + self, ConnectorFields, Mandates, PaymentMethodType, RequiredFieldFinal, + SupportedConnectorsForMandate, SupportedPaymentMethodTypesForMandate, + SupportedPaymentMethodsForMandate, +}; + +impl Default for Mandates { + fn default() -> Self { + Self { + supported_payment_methods: SupportedPaymentMethodsForMandate(HashMap::from([ + ( + enums::PaymentMethod::PayLater, + SupportedPaymentMethodTypesForMandate(HashMap::from([( + enums::PaymentMethodType::Klarna, + SupportedConnectorsForMandate { + connector_list: HashSet::from([enums::Connector::Adyen]), + }, + )])), + ), + ( + enums::PaymentMethod::Wallet, + SupportedPaymentMethodTypesForMandate(HashMap::from([ + ( + enums::PaymentMethodType::GooglePay, + SupportedConnectorsForMandate { + connector_list: HashSet::from([ + enums::Connector::Stripe, + enums::Connector::Adyen, + enums::Connector::Globalpay, + enums::Connector::Multisafepay, + enums::Connector::Bankofamerica, + enums::Connector::Novalnet, + enums::Connector::Noon, + enums::Connector::Cybersource, + enums::Connector::Wellsfargo, + ]), + }, + ), + ( + enums::PaymentMethodType::ApplePay, + SupportedConnectorsForMandate { + connector_list: HashSet::from([ + enums::Connector::Stripe, + enums::Connector::Adyen, + enums::Connector::Bankofamerica, + enums::Connector::Cybersource, + enums::Connector::Novalnet, + enums::Connector::Wellsfargo, + ]), + }, + ), + ])), + ), + ( + enums::PaymentMethod::Card, + SupportedPaymentMethodTypesForMandate(HashMap::from([ + ( + enums::PaymentMethodType::Credit, + SupportedConnectorsForMandate { + connector_list: HashSet::from([ + enums::Connector::Aci, + enums::Connector::Adyen, + enums::Connector::Authorizedotnet, + enums::Connector::Globalpay, + enums::Connector::Worldpay, + enums::Connector::Multisafepay, + enums::Connector::Nexinets, + enums::Connector::Noon, + enums::Connector::Novalnet, + enums::Connector::Payme, + enums::Connector::Stripe, + enums::Connector::Bankofamerica, + enums::Connector::Cybersource, + enums::Connector::Wellsfargo, + ]), + }, + ), + ( + enums::PaymentMethodType::Debit, + SupportedConnectorsForMandate { + connector_list: HashSet::from([ + enums::Connector::Aci, + enums::Connector::Adyen, + enums::Connector::Authorizedotnet, + enums::Connector::Globalpay, + enums::Connector::Worldpay, + enums::Connector::Multisafepay, + enums::Connector::Nexinets, + enums::Connector::Noon, + enums::Connector::Novalnet, + enums::Connector::Payme, + enums::Connector::Stripe, + ]), + }, + ), + ])), + ), + ])), + update_mandate_supported: SupportedPaymentMethodsForMandate(HashMap::default()), + } + } +} + +impl Default for settings::RequiredFields { + fn default() -> Self { + Self(HashMap::from([ + ( + enums::PaymentMethod::Card, + PaymentMethodType(HashMap::from([ + ( + enums::PaymentMethodType::Debit, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Aci, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ] + ), + } + ), + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ] + ), + } + ), + ( + enums::Connector::Airwallex, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Authorizedotnet, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Bambora, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ) + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Bankofamerica, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Billwerk, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Bluesnap, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ) + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Boku, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Braintree, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Checkout, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Coinbase, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ) + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Cybersource, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common:HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Dlocal, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ) + ] + ), + common:HashMap::new(), + } + ), + #[cfg(feature = "dummy_connector")] + ( + enums::Connector::DummyConnector1, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + #[cfg(feature = "dummy_connector")] + ( + enums::Connector::DummyConnector2, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + #[cfg(feature = "dummy_connector")] + ( + enums::Connector::DummyConnector3, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + #[cfg(feature = "dummy_connector")] + ( + enums::Connector::DummyConnector4, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + #[cfg(feature = "dummy_connector")] + ( + enums::Connector::DummyConnector5, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + #[cfg(feature = "dummy_connector")] + ( + enums::Connector::DummyConnector6, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + #[cfg(feature = "dummy_connector")] + ( + enums::Connector::DummyConnector7, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Elavon, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Fiserv, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Fiuu, + RequiredFieldFinal { + mandate: HashMap::from([ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ]), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Forte, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ] + ), + common:HashMap::new(), + } + ), + ( + enums::Connector::Globalpay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ]), + } + ), + ( + enums::Connector::Helcim, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Iatapay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Mollie, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Multisafepay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate:HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.line2".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line2".to_string(), + display_name: "line2".to_string(), + field_type: enums::FieldType::UserAddressLine2, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Nexinets, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Nexixpay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.line2".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line2".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine2, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "first_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "last_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Nmi, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "billing_zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Noon, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ] + ), + } + ), + ( + enums::Connector::Novalnet, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "first_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "last_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email_address".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ] + ), + } + ), + ( + enums::Connector::Nuvei, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Paybox, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ) + + ] + ), + } + ), + ( + enums::Connector::Payme, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ] + ), + } + ), + ( + enums::Connector::Paypal, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Payu, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Powertranz, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Rapyd, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Shift4, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Square, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Stax, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common:HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Trustpay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ] + ), + common: HashMap::new() + } + ), + ( + enums::Connector::Tsys, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + common: HashMap::new() + } + ), + ( + enums::Connector::Wellsfargo, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common:HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Worldline, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ) + ]), + common: HashMap::new(), + } + ), + ( + enums::Connector::Worldpay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: { + let mut pmd_fields = HashMap::from([ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ) + ]); + pmd_fields.extend(get_worldpay_billing_required_fields()); + pmd_fields + }, + common: HashMap::new(), + } + ), + ( + enums::Connector::Zen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ]), + common: HashMap::new(), + } + ), + ]), + }, + ), + ( + enums::PaymentMethodType::Credit, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Aci, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ] + ), + } + ), + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ] + ), + } + ), + ( + enums::Connector::Airwallex, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Authorizedotnet, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Bambora, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ) + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Bankofamerica, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ] + ), + } + ), + ( + enums::Connector::Billwerk, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Bluesnap, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ) + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Boku, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Braintree, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Checkout, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Coinbase, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ) + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Cybersource, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate:HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Dlocal, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ) + ] + ), + common:HashMap::new(), + } + ), + #[cfg(feature = "dummy_connector")] + ( + enums::Connector::DummyConnector1, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + #[cfg(feature = "dummy_connector")] + ( + enums::Connector::DummyConnector2, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + #[cfg(feature = "dummy_connector")] + ( + enums::Connector::DummyConnector3, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + #[cfg(feature = "dummy_connector")] + ( + enums::Connector::DummyConnector4, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + #[cfg(feature = "dummy_connector")] + ( + enums::Connector::DummyConnector5, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + #[cfg(feature = "dummy_connector")] + ( + enums::Connector::DummyConnector6, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + #[cfg(feature = "dummy_connector")] + ( + enums::Connector::DummyConnector7, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Elavon, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Fiserv, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Fiuu, + RequiredFieldFinal { + mandate: HashMap::from([ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ]), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Forte, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ] + ), + common:HashMap::new(), + } + ), + ( + enums::Connector::Globalpay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ]), + } + ), + ( + enums::Connector::Helcim, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Iatapay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Mollie, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Multisafepay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate:HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.line2".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line2".to_string(), + display_name: "line2".to_string(), + field_type: enums::FieldType::UserAddressLine2, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Nexinets, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Nexixpay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.line2".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line2".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine2, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "first_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "last_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Nmi, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "billing_zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Noon, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ] + ), + } + ), + ( + enums::Connector::Novalnet, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "first_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "last_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email_address".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ] + ), + } + ), + ( + enums::Connector::Nuvei, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ),( + enums::Connector::Paybox, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ) + + ] + ), + } + ), + ( + enums::Connector::Payme, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ] + ), + } + ), + ( + enums::Connector::Paypal, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Payu, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Powertranz, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Rapyd, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Shift4, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Square, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Stax, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common:HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Trustpay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ] + ), + common: HashMap::new() + } + ), + ( + enums::Connector::Tsys, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ) + ] + ), + common: HashMap::new() + } + ), + ( + enums::Connector::Wellsfargo, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate:HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Worldline, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ) + ]), + common: HashMap::new(), + } + ), + ( + enums::Connector::Worldpay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: { + let mut pmd_fields = HashMap::from([ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ) + ]); + pmd_fields.extend(get_worldpay_billing_required_fields()); + pmd_fields + }, + common: HashMap::new(), + } + ), + ( + enums::Connector::Zen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ]), + common: HashMap::new(), + } + ), + ]), + }, + ), + + ])), + ), + ( + enums::PaymentMethod::BankRedirect, + PaymentMethodType(HashMap::from([ + ( + enums::PaymentMethodType::OpenBankingUk, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Volt, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ]), + common: HashMap::new(), + } + ), + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap:: from([ + ( + "payment_method_data.bank_redirect.open_banking_uk.issuer".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.open_banking_uk.issuer".to_string(), + display_name: "issuer".to_string(), + field_type: enums::FieldType::UserBank, + value: None, + } + ) + ]), + common: HashMap::new(), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::Trustly, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::OnlineBankingCzechRepublic, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "payment_method_data.bank_redirect.open_banking_czech_republic.issuer".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.open_banking_czech_republic.issuer".to_string(), + display_name: "issuer".to_string(), + field_type: enums::FieldType::UserBank, + value: None, + } + ) + ]), + common: HashMap::new(), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::OnlineBankingFinland, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ]), + common: HashMap::new(), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::OnlineBankingPoland, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "payment_method_data.bank_redirect.open_banking_poland.issuer".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.open_banking_poland.issuer".to_string(), + display_name: "issuer".to_string(), + field_type: enums::FieldType::UserBank, + value: None, + } + ), + + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ]), + common: HashMap::new(), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::OnlineBankingSlovakia, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "payment_method_data.bank_redirect.open_banking_slovakia.issuer".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.open_banking_slovakia.issuer".to_string(), + display_name: "issuer".to_string(), + field_type: enums::FieldType::UserBank, + value: None, + } + ), + ]), + common: HashMap::new(), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::OnlineBankingFpx, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "payment_method_data.bank_redirect.open_banking_fpx.issuer".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.open_banking_fpx.issuer".to_string(), + display_name: "issuer".to_string(), + field_type: enums::FieldType::UserBank, + value: None, + } + ) + ]), + common: HashMap::new(), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::OnlineBankingThailand, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "payment_method_data.bank_redirect.open_banking_thailand.issuer".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.open_banking_thailand.issuer".to_string(), + display_name: "issuer".to_string(), + field_type: enums::FieldType::UserBank, + value: None, + } + ) + ]), + common: HashMap::new(), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::Bizum, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::Przelewy24, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ) + ]), + common: HashMap::new(), + } + )]), + }, + ), + ( + enums::PaymentMethodType::BancontactCard, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Mollie, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::from([ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ) + ]), + non_mandate: HashMap::new(), + common: HashMap::from([ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_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, + } + ), + ]), + } + ), + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ + ( + "payment_method_data.bank_redirect.bancontact_card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.bancontact_card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.bank_redirect.bancontact_card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.bancontact_card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.bank_redirect.bancontact_card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.bancontact_card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ]), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::Giropay, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Aci, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "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![ + "DE".to_string(), + ]}, + value: None, + } + ) + ]), + common: HashMap::new(), + } + ), + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Globalpay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ + ("billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry { + options: vec![ + "DE".to_string(), + ] + }, + value: None, + } + ) + ]), + } + ), + ( + enums::Connector::Mollie, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Nuvei, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate:HashMap::from([ + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "DE".to_string(), + ] + }, + value: None, + } + )] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Paypal, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "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![ + "DE".to_string(), + ] + }, + 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(), + } + ), + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "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(), + } + ), + ( + enums::Connector::Shift4, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Trustpay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry { + options: vec![ + "DE".to_string(), + ] + }, + value: None, + } + ), + ]), + common: HashMap::new(), + } + ), + ]), + }, + ), + ( + enums::PaymentMethodType::Ideal, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Aci, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "payment_method_data.bank_redirect.ideal.bank_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.ideal.bank_name".to_string(), + display_name: "bank_name".to_string(), + field_type: enums::FieldType::UserBank, + 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![ + "NL".to_string(), + ] + }, + value: None, + } + ) + ]), + common: HashMap::new(), + } + ), + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ + ( + "payment_method_data.bank_redirect.ideal.bank_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.ideal.bank_name".to_string(), + display_name: "bank_name".to_string(), + field_type: enums::FieldType::UserBank, + value: None, + } + ), + + ]), + } + ), + ( + enums::Connector::Globalpay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Mollie, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Nexinets, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Nuvei, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "NL".to_string(), + ] + }, + value: None, + } + ) + ]), + common: HashMap::new(), + } + ), + ( + enums::Connector::Shift4, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "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![ + "NL".to_string(), + ] + }, + value: None, + } + ) + ]), + common: HashMap::new(), + } + ), + ( + enums::Connector::Paypal, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "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, + } + ), + ( + "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![ + "NL".to_string(), + ] + }, + value: None, + } + ) + ]), + common: HashMap::new(), + } + ), + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::from([ + ( + "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.billing.address.last_name".to_string(), + display_name: "billing_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "billing_email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ) + ]), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Trustpay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "NL".to_string(), + ] + }, + value: None, + } + ), + ]), + common: HashMap::new(), + } + ), + ]), + }, + ), + ( + enums::PaymentMethodType::Sofort, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Aci, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ("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![ + "ES".to_string(), + "GB".to_string(), + "SE".to_string(), + "AT".to_string(), + "NL".to_string(), + "DE".to_string(), + "CH".to_string(), + "BE".to_string(), + "FR".to_string(), + "FI".to_string(), + "IT".to_string(), + "PL".to_string(), + ] + }, + value: None, + } + ) + ]), + common: HashMap::new(), + } + ), + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Globalpay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ + ("billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry { + options: vec![ + "AT".to_string(), + "BE".to_string(), + "DE".to_string(), + "ES".to_string(), + "IT".to_string(), + "NL".to_string(), + ] + }, + value: None, + } + ) + ]), + } + ), + ( + enums::Connector::Mollie, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Nexinets, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Nuvei, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate:HashMap::from([ + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ES".to_string(), + "GB".to_string(), + "IT".to_string(), + "DE".to_string(), + "FR".to_string(), + "AT".to_string(), + "BE".to_string(), + "NL".to_string(), + "BE".to_string(), + "SK".to_string(), + ] + }, + value: None, + } + )] + ), + common: HashMap::new(), + } + ), + ( + enums::Connector::Paypal, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( "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![ + "ES".to_string(), + "GB".to_string(), + "AT".to_string(), + "NL".to_string(), + "DE".to_string(), + "BE".to_string(), + ] + }, + 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(), + } + ), + ( + enums::Connector::Shift4, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::from([ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ) + ]), + non_mandate : HashMap::new(), + common: HashMap::from([ + ("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![ + "ES".to_string(), + "AT".to_string(), + "NL".to_string(), + "DE".to_string(), + "BE".to_string(), + ] + }, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "account_holder_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "account_holder_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + )]), + } + ), + ( + enums::Connector::Trustpay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry { + options: vec![ + "ES".to_string(), + "GB".to_string(), + "SE".to_string(), + "AT".to_string(), + "NL".to_string(), + "DE".to_string(), + "CH".to_string(), + "BE".to_string(), + "FR".to_string(), + "FI".to_string(), + "IT".to_string(), + "PL".to_string(), + ] + }, + value: None, + } + ), + ]), + common: HashMap::new(), + } + ), + ]), + }, + ), + ( + enums::PaymentMethodType::Eps, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ + ( + "payment_method_data.bank_redirect.eps.bank_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.eps.bank_name".to_string(), + display_name: "bank_name".to_string(), + field_type: enums::FieldType::UserBank, + value: None, + } + ) + ]), + } + ), + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "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.billing.address.last_name".to_string(), + display_name: "billing_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ]), + common: HashMap::new(), + } + ), + ( + enums::Connector::Aci, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "bank_account_country".to_string(), + field_type: enums::FieldType::UserCountry { + options: vec![ + "AT".to_string(), + ] + }, + value: None, + } + ) + ]), + common: HashMap::new(), + } + ), + ( + enums::Connector::Globalpay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry { + options: vec![ + "AT".to_string(), + ] + }, + value: None, + } + ) + ]) + } + ), + ( + enums::Connector::Mollie, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate:HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Paypal, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "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.billing.address.last_name".to_string(), + display_name: "billing_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "bank_account_country".to_string(), + field_type: enums::FieldType::UserCountry { + options: vec![ + "AT".to_string(), + ] + }, + value: None, + } + ) + ]), + common: HashMap::new(), + } + ), + ( + enums::Connector::Trustpay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "AT".to_string(), + ] + }, + value: None, + } + ), + ]), + common: HashMap::new(), + } + ), + ( + enums::Connector::Shift4, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate:HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Nuvei, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "AT".to_string(), + ] + }, + value: None, + } + )] + ), + common: HashMap::new(), + } + ), + ]), + }, + ), + ( + enums::PaymentMethodType::Blik, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ + ( + "payment_method_data.bank_redirect.blik.blik_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.blik.blik_code".to_string(), + display_name: "blik_code".to_string(), + field_type: enums::FieldType::UserBlikCode, + value: None, + } + ) + ]), + } + ), + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ + ( + "payment_method_data.bank_redirect.blik.blik_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_redirect.blik.blik_code".to_string(), + display_name: "blik_code".to_string(), + field_type: enums::FieldType::UserBlikCode, + value: None, + } + ) + ]), + } + ), + ( + enums::Connector::Trustpay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ]), + } + ) + ]), + }, + ), + ])), + ), + ( + enums::PaymentMethod::Wallet, + PaymentMethodType(HashMap::from([ + ( + enums::PaymentMethodType::ApplePay, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Bankofamerica, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Cybersource, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Novalnet, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "first_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "last_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email_address".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ] + ), + } + ), + ( + enums::Connector::Wellsfargo, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "shipping.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.first_name".to_string(), + display_name: "shipping_first_name".to_string(), + field_type: enums::FieldType::UserShippingName, + value: None, + } + ), + ( + "shipping.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.last_name".to_string(), + display_name: "shipping_last_name".to_string(), + field_type: enums::FieldType::UserShippingName, + value: None, + } + ), + ( + "shipping.address.city".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserShippingAddressCity, + value: None, + } + ), + ( + "shipping.address.state".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserShippingAddressState, + value: None, + } + ), + ( + "shipping.address.zip".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserShippingAddressPincode, + value: None, + } + ), + ( + "shipping.address.country".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserShippingAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ( + "shipping.address.line1".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserShippingAddressLine1, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } + ), + + ]), + }, + ), + ( + enums::PaymentMethodType::GooglePay, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Bankofamerica, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Bluesnap, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Noon, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Novalnet, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "first_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "last_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email_address".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ] + ), + } + ), + ( + enums::Connector::Nuvei, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Airwallex, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Authorizedotnet, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Checkout, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Globalpay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Multisafepay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.line2".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line2".to_string(), + display_name: "line2".to_string(), + field_type: enums::FieldType::UserAddressLine2, + value: None, + } + )]), + common: HashMap::new(), + } + ), + ( + enums::Connector::Cybersource, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ) + ] + ), + } + ), + ( + enums::Connector::Payu, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Rapyd, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Trustpay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Wellsfargo, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "email".to_string(), + RequiredFieldInfo { + required_field: "email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "shipping.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.first_name".to_string(), + display_name: "shipping_first_name".to_string(), + field_type: enums::FieldType::UserShippingName, + value: None, + } + ), + ( + "shipping.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.last_name".to_string(), + display_name: "shipping_last_name".to_string(), + field_type: enums::FieldType::UserShippingName, + value: None, + } + ), + ( + "shipping.address.city".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserShippingAddressCity, + value: None, + } + ), + ( + "shipping.address.state".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserShippingAddressState, + value: None, + } + ), + ( + "shipping.address.zip".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserShippingAddressPincode, + value: None, + } + ), + ( + "shipping.address.country".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserShippingAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ( + "shipping.address.line1".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserShippingAddressLine1, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } + ), + ]), + }, + ), + ( + enums::PaymentMethodType::WeChatPay, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ]), + }, + ), + ( + enums::PaymentMethodType::AliPay, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ]), + }, + ), + ( + enums::PaymentMethodType::AliPayHk, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ]), + }, + ), + ( + enums::PaymentMethodType::Cashapp, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::MbWay, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + common: HashMap::new(), + non_mandate: HashMap::from([ + ( + "payment_method_data.billing.phone.number".to_string(), + RequiredFieldInfo { + required_field: "billing.phone.number".to_string(), + display_name: "phone_number".to_string(), + field_type: enums::FieldType::UserPhoneNumber, + value: None, + } + ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), + ] + ), + } + ), + ]), + }, + ), + ( + enums::PaymentMethodType::KakaoPay, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ]), + }, + ), + ( + enums::PaymentMethodType::Twint, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ]), + }, + ), + ( + enums::PaymentMethodType::Gcash, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ]), + }, + ), + ( + enums::PaymentMethodType::Vipps, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ]), + }, + ), + ( + enums::PaymentMethodType::Dana, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ]), + }, + ), + ( + enums::PaymentMethodType::Momo, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ]), + }, + ), + ( + enums::PaymentMethodType::Swish, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ]), + }, + ), + ( + enums::PaymentMethodType::TouchNGo, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ]), + }, + ), + ( + // Added shipping fields for the SDK flow to accept it from wallet directly, + // this won't show up in SDK in payment's sheet but will be used in the background + enums::PaymentMethodType::Paypal, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + )] + ), + } + ), + ( + enums::Connector::Braintree, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Novalnet, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "first_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "last_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email_address".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ] + ), + } + ), + ( + enums::Connector::Paypal, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new( + ), + common: HashMap::from( + [ + ( + "shipping.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.first_name".to_string(), + display_name: "shipping_first_name".to_string(), + field_type: enums::FieldType::UserShippingName, + value: None, + } + ), + ( + "shipping.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.last_name".to_string(), + display_name: "shipping_last_name".to_string(), + field_type: enums::FieldType::UserShippingName, + value: None, + } + ), + ( + "shipping.address.city".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserShippingAddressCity, + value: None, + } + ), + ( + "shipping.address.state".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserShippingAddressState, + value: None, + } + ), + ( + "shipping.address.zip".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserShippingAddressPincode, + value: None, + } + ), + ( + "shipping.address.country".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserShippingAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ( + "shipping.address.line1".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserShippingAddressLine1, + value: None, + } + ), + ] + ), + } + ), + ]), + }, + ), + ( + enums::PaymentMethodType::Mifinity, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Mifinity, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ + ( + "payment_method_data.wallet.mifinity.date_of_birth".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.wallet.mifinity.date_of_birth".to_string(), + display_name: "date_of_birth".to_string(), + field_type: enums::FieldType::UserDateOfBirth, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "first_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "last_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.phone.number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.number".to_string(), + display_name: "phone".to_string(), + field_type: enums::FieldType::UserPhoneNumber, + value: None, + } + ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "nationality".to_string(), + field_type: enums::FieldType::UserCountry{ + options: vec![ + "BR".to_string(), + "CN".to_string(), + "SG".to_string(), + "MY".to_string(), + "DE".to_string(), + "CH".to_string(), + "DK".to_string(), + "GB".to_string(), + "ES".to_string(), + "AD".to_string(), + "GI".to_string(), + "FI".to_string(), + "FR".to_string(), + "GR".to_string(), + "HR".to_string(), + "IT".to_string(), + "JP".to_string(), + "MX".to_string(), + "AR".to_string(), + "CO".to_string(), + "CL".to_string(), + "PE".to_string(), + "VE".to_string(), + "UY".to_string(), + "PY".to_string(), + "BO".to_string(), + "EC".to_string(), + "GT".to_string(), + "HN".to_string(), + "SV".to_string(), + "NI".to_string(), + "CR".to_string(), + "PA".to_string(), + "DO".to_string(), + "CU".to_string(), + "PR".to_string(), + "NL".to_string(), + "NO".to_string(), + "PL".to_string(), + "PT".to_string(), + "SE".to_string(), + "RU".to_string(), + "TR".to_string(), + "TW".to_string(), + "HK".to_string(), + "MO".to_string(), + "AX".to_string(), + "AL".to_string(), + "DZ".to_string(), + "AS".to_string(), + "AO".to_string(), + "AI".to_string(), + "AG".to_string(), + "AM".to_string(), + "AW".to_string(), + "AU".to_string(), + "AT".to_string(), + "AZ".to_string(), + "BS".to_string(), + "BH".to_string(), + "BD".to_string(), + "BB".to_string(), + "BE".to_string(), + "BZ".to_string(), + "BJ".to_string(), + "BM".to_string(), + "BT".to_string(), + "BQ".to_string(), + "BA".to_string(), + "BW".to_string(), + "IO".to_string(), + "BN".to_string(), + "BG".to_string(), + "BF".to_string(), + "BI".to_string(), + "KH".to_string(), + "CM".to_string(), + "CA".to_string(), + "CV".to_string(), + "KY".to_string(), + "CF".to_string(), + "TD".to_string(), + "CX".to_string(), + "CC".to_string(), + "KM".to_string(), + "CG".to_string(), + "CK".to_string(), + "CI".to_string(), + "CW".to_string(), + "CY".to_string(), + "CZ".to_string(), + "DJ".to_string(), + "DM".to_string(), + "EG".to_string(), + "GQ".to_string(), + "ER".to_string(), + "EE".to_string(), + "ET".to_string(), + "FK".to_string(), + "FO".to_string(), + "FJ".to_string(), + "GF".to_string(), + "PF".to_string(), + "TF".to_string(), + "GA".to_string(), + "GM".to_string(), + "GE".to_string(), + "GH".to_string(), + "GL".to_string(), + "GD".to_string(), + "GP".to_string(), + "GU".to_string(), + "GG".to_string(), + "GN".to_string(), + "GW".to_string(), + "GY".to_string(), + "HT".to_string(), + "HM".to_string(), + "VA".to_string(), + "IS".to_string(), + "IN".to_string(), + "ID".to_string(), + "IE".to_string(), + "IM".to_string(), + "IL".to_string(), + "JE".to_string(), + "JO".to_string(), + "KZ".to_string(), + "KE".to_string(), + "KI".to_string(), + "KW".to_string(), + "KG".to_string(), + "LA".to_string(), + "LV".to_string(), + "LB".to_string(), + "LS".to_string(), + "LI".to_string(), + "LT".to_string(), + "LU".to_string(), + "MK".to_string(), + "MG".to_string(), + "MW".to_string(), + "MV".to_string(), + "ML".to_string(), + "MT".to_string(), + "MH".to_string(), + "MQ".to_string(), + "MR".to_string(), + "MU".to_string(), + "YT".to_string(), + "FM".to_string(), + "MD".to_string(), + "MC".to_string(), + "MN".to_string(), + "ME".to_string(), + "MS".to_string(), + "MA".to_string(), + "MZ".to_string(), + "NA".to_string(), + "NR".to_string(), + "NP".to_string(), + "NC".to_string(), + "NZ".to_string(), + "NE".to_string(), + "NG".to_string(), + "NU".to_string(), + "NF".to_string(), + "MP".to_string(), + "OM".to_string(), + "PK".to_string(), + "PW".to_string(), + "PS".to_string(), + "PG".to_string(), + "PH".to_string(), + "PN".to_string(), + "QA".to_string(), + "RE".to_string(), + "RO".to_string(), + "RW".to_string(), + "BL".to_string(), + "SH".to_string(), + "KN".to_string(), + "LC".to_string(), + "MF".to_string(), + "PM".to_string(), + "VC".to_string(), + "WS".to_string(), + "SM".to_string(), + "ST".to_string(), + "SA".to_string(), + "SN".to_string(), + "RS".to_string(), + "SC".to_string(), + "SL".to_string(), + "SX".to_string(), + "SK".to_string(), + "SI".to_string(), + "SB".to_string(), + "SO".to_string(), + "ZA".to_string(), + "GS".to_string(), + "KR".to_string(), + "LK".to_string(), + "SR".to_string(), + "SJ".to_string(), + "SZ".to_string(), + "TH".to_string(), + "TL".to_string(), + "TG".to_string(), + "TK".to_string(), + "TO".to_string(), + "TT".to_string(), + "TN".to_string(), + "TM".to_string(), + "TC".to_string(), + "TV".to_string(), + "UG".to_string(), + "UA".to_string(), + "AE".to_string(), + "UZ".to_string(), + "VU".to_string(), + "VN".to_string(), + "VG".to_string(), + "VI".to_string(), + "WF".to_string(), + "EH".to_string(), + "ZM".to_string(), + ] + }, + value: None, + } + + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email_address".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "payment_method_data.wallet.mifinity.language_preference".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.wallet.mifinity.language_preference".to_string(), + display_name: "language_preference".to_string(), + field_type: enums::FieldType::LanguagePreference{ + options: vec![ + "BR".to_string(), + "PT_BR".to_string(), + "CN".to_string(), + "ZH_CN".to_string(), + "DE".to_string(), + "DK".to_string(), + "DA".to_string(), + "DA_DK".to_string(), + "EN".to_string(), + "ES".to_string(), + "FI".to_string(), + "FR".to_string(), + "GR".to_string(), + "EL".to_string(), + "EL_GR".to_string(), + "HR".to_string(), + "IT".to_string(), + "JP".to_string(), + "JA".to_string(), + "JA_JP".to_string(), + "LA".to_string(), + "ES_LA".to_string(), + "NL".to_string(), + "NO".to_string(), + "PL".to_string(), + "PT".to_string(), + "RU".to_string(), + "SV".to_string(), + "SE".to_string(), + "SV_SE".to_string(), + "ZH".to_string(), + "TW".to_string(), + "ZH_TW".to_string(), + ] + }, + value: None, + } + ), + ]), + } + ), + ]), + } + ), + ])), + ), + ( + enums::PaymentMethod::PayLater, + PaymentMethodType(HashMap::from([ + ( + enums::PaymentMethodType::AfterpayClearpay, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate : HashMap::new(), + non_mandate: HashMap::from([ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "GB".to_string(), + "AU".to_string(), + "CA".to_string(), + "US".to_string(), + "NZ".to_string(), + ] + }, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "shipping.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.first_name".to_string(), + display_name: "shipping_first_name".to_string(), + field_type: enums::FieldType::UserShippingName, + value: None, + } + ), + ( + "shipping.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.last_name".to_string(), + display_name: "shipping_last_name".to_string(), + field_type: enums::FieldType::UserShippingName, + value: None, + } + ), + ( + "shipping.address.city".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserShippingAddressCity, + value: None, + } + ), + ( + "shipping.address.state".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserShippingAddressState, + value: None, + } + ), + ( + "shipping.address.zip".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserShippingAddressPincode, + value: None, + } + ), + ( + "shipping.address.country".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserShippingAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ( + "shipping.address.line1".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserShippingAddressLine1, + value: None, + } + ), + ]), + common : HashMap::new(), + } + ), + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate : HashMap::new(), + non_mandate: HashMap::from([ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.line2".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line2".to_string(), + display_name: "line2".to_string(), + field_type: enums::FieldType::UserAddressLine2, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "GB".to_string(), + "AU".to_string(), + "CA".to_string(), + "US".to_string(), + "NZ".to_string(), + ] + }, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "shipping.address.city".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserShippingAddressCity, + value: None, + } + ), + ( + "shipping.address.zip".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserShippingAddressPincode, + value: None, + } + ), + ( + "shipping.address.country".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserShippingAddressCountry{ + options: vec![ + "GB".to_string(), + "AU".to_string(), + "CA".to_string(), + "US".to_string(), + "NZ".to_string(), + ] + }, + value: None, + } + ), + ( + "shipping.address.line1".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserShippingAddressLine1, + value: None, + } + ), + ( + "shipping.address.line2".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.line2".to_string(), + display_name: "line2".to_string(), + field_type: enums::FieldType::UserShippingAddressLine2, + value: None, + } + ), + ]), + common : HashMap::new(), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::Klarna, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate : HashMap::new(), + non_mandate: HashMap::from([ + ( "payment_method_data.pay_later.klarna.billing_country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.pay_later.klarna.billing_country".to_string(), + display_name: "billing_country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "AU".to_string(), + "AT".to_string(), + "BE".to_string(), + "CA".to_string(), + "CZ".to_string(), + "DK".to_string(), + "FI".to_string(), + "FR".to_string(), + "GR".to_string(), + "DE".to_string(), + "IE".to_string(), + "IT".to_string(), + "NL".to_string(), + "NZ".to_string(), + "NO".to_string(), + "PL".to_string(), + "PT".to_string(), + "RO".to_string(), + "ES".to_string(), + "SE".to_string(), + "CH".to_string(), + "GB".to_string(), + "US".to_string(), + ] + }, + value: None, + }), + ("billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + }) + ]), + common : HashMap::new(), + } + ), + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate : HashMap::new(), + non_mandate: HashMap::new(), + common : HashMap::from([ + ( "payment_method_data.pay_later.klarna.billing_country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.pay_later.klarna.billing_country".to_string(), + display_name: "billing_country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + }), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ]), + } + ), + ( + enums::Connector::Klarna, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "AU".to_string(), + "AT".to_string(), + "BE".to_string(), + "CA".to_string(), + "CZ".to_string(), + "DK".to_string(), + "FI".to_string(), + "FR".to_string(), + "DE".to_string(), + "GR".to_string(), + "IE".to_string(), + "IT".to_string(), + "NL".to_string(), + "NZ".to_string(), + "NO".to_string(), + "PL".to_string(), + "PT".to_string(), + "ES".to_string(), + "SE".to_string(), + "CH".to_string(), + "GB".to_string(), + "US".to_string(), + ] + }, + value: None, + } + ) + ]), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::Affirm, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "US".to_string(), + ] + }, + value: None, + } + ), + ( + "billing.phone.number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.number".to_string(), + display_name: "phone_number".to_string(), + field_type: enums::FieldType::UserPhoneNumber, + value: None, + } + ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.line2".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line2".to_string(), + display_name: "line2".to_string(), + field_type: enums::FieldType::UserAddressLine2, + value: None, + } + ), + ( + "shipping.address.line1".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "shipping.address.line2".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.line2".to_string(), + display_name: "line2".to_string(), + field_type: enums::FieldType::UserAddressLine2, + value: None, + } + ), + ( + "shipping.address.zip".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserShippingAddressPincode, + value: None, + } + ), + ( + "shipping.address.city".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserShippingAddressCity, + value: None, + } + ), + ( + "shipping.address.country".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserCountry { + options: vec![ + "US".to_string(), + ]}, + value: None, + } + ), + + ] + ), + common: HashMap::new(), + } + ), + ]), + }, + ), + ( + enums::PaymentMethodType::PayBright, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "CA".to_string(), + ] + }, + value: None, + } + ), + ( + "payment_method_data.billing.phone.number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.number".to_string(), + display_name: "phone_number".to_string(), + field_type: enums::FieldType::UserPhoneNumber, + value: None, + } + ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.line2".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line2".to_string(), + display_name: "line2".to_string(), + field_type: enums::FieldType::UserAddressLine2, + value: None, + } + ), + ( + "shipping.address.city".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserShippingAddressCity, + value: None, + } + ), + ( + "shipping.address.zip".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserShippingAddressPincode, + value: None, + } + ), + ( + "shipping.address.country".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserShippingAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ( + "shipping.address.line1".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserShippingAddressLine1, + value: None, + } + ), + ( + "shipping.address.line2".to_string(), + RequiredFieldInfo { + required_field: "shipping.address.line2".to_string(), + display_name: "line2".to_string(), + field_type: enums::FieldType::UserShippingAddressLine2, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } + ), + ]), + }, + ), + ( + enums::PaymentMethodType::Walley, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "billing.phone.number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.number".to_string(), + display_name: "phone".to_string(), + field_type: enums::FieldType::UserPhoneNumber, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "DK".to_string(), + "FI".to_string(), + "NO".to_string(), + "SE".to_string(), + ]}, + value: None, + } + ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } + ), + ]), + }, + ), + ( + enums::PaymentMethodType::Alma, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "FR".to_string(), + ] + }, + value: None, + } + ), + ( + "payment_method_data.billing.phone.number".to_string(), + RequiredFieldInfo { + required_field: "billing.phone.number".to_string(), + display_name: "phone_number".to_string(), + field_type: enums::FieldType::UserPhoneNumber, + value: None, + } + ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.line2".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line2".to_string(), + display_name: "line2".to_string(), + field_type: enums::FieldType::UserAddressLine2, + value: None, + } + ) + ] + ), + common: HashMap::new(), + } + ), + ]), + }, + ), + ( + enums::PaymentMethodType::Atome, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "MY".to_string(), + "SG".to_string() + ] + }, + value: None, + } + ), + ( + "payment_method_data.billing.phone.number".to_string(), + RequiredFieldInfo { + required_field: "billing.phone.number".to_string(), + display_name: "phone_number".to_string(), + field_type: enums::FieldType::UserPhoneNumber, + value: None, + } + ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.line2".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line2".to_string(), + display_name: "line2".to_string(), + field_type: enums::FieldType::UserAddressLine2, + value: None, + } + ) + ] + ), + common: HashMap::new(), + } + ), + ]), + }, + ), + ])), + ), + ( + enums::PaymentMethod::Crypto, + PaymentMethodType(HashMap::from([ + ( + enums::PaymentMethodType::CryptoCurrency, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Cryptopay, + RequiredFieldFinal { + mandate : HashMap::new(), + non_mandate: HashMap::from([ + ( + "payment_method_data.crypto.pay_currency".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.crypto.pay_currency".to_string(), + display_name: "currency".to_string(), + field_type: enums::FieldType::UserCurrency{ + options: vec![ + "BTC".to_string(), + "LTC".to_string(), + "ETH".to_string(), + "XRP".to_string(), + "XLM".to_string(), + "BCH".to_string(), + "ADA".to_string(), + "SOL".to_string(), + "SHIB".to_string(), + "TRX".to_string(), + "DOGE".to_string(), + "BNB".to_string(), + "USDT".to_string(), + "USDC".to_string(), + "DAI".to_string(), + ] + }, + value: None, + } + ), + ( + "payment_method_data.crypto.network".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.crypto.network".to_string(), + display_name: "network".to_string(), + field_type: enums::FieldType::UserCryptoCurrencyNetwork, + value: None, + } + ), + ]), + common : HashMap::new(), + } + ), + ]), + }, + ), + ])), + ), + ( + enums::PaymentMethod::Voucher, + PaymentMethodType(HashMap::from([ + ( + enums::PaymentMethodType::Boleto, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate : HashMap::new(), + non_mandate : HashMap::from([ + ( + "payment_method_data.voucher.boleto.social_security_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.voucher.boleto.social_security_number".to_string(), + display_name: "social_security_number".to_string(), + field_type: enums::FieldType::Text, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "BR".to_string(), + ] + }, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.line2".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line2".to_string(), + display_name: "line2".to_string(), + field_type: enums::FieldType::UserAddressLine2, + value: None, + } + ), + ]), + common : HashMap::new(), + } + ), + ( + enums::Connector::Zen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::Alfamart, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate : HashMap::new(), + non_mandate : HashMap::from([ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ) + ]), + common : HashMap::new(), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::Indomaret, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate : HashMap::new(), + non_mandate : HashMap::from([ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ) + ]), + common : HashMap::new(), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::Oxxo, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate : HashMap::new(), + non_mandate : HashMap::new(), + common : HashMap::new(), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::SevenEleven, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate : HashMap::new(), + non_mandate : HashMap::from([ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.phone.number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.number".to_string(), + display_name: "phone".to_string(), + field_type: enums::FieldType::UserPhoneNumber, + value: None, + } + ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ) + ] + ), + common : HashMap::new(), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::Lawson, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate : HashMap::new(), + non_mandate : HashMap::from([ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.phone.number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.number".to_string(), + display_name: "phone".to_string(), + field_type: enums::FieldType::UserPhoneNumber, + value: None, + } + ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), + ] + ), + common : HashMap::new(), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::MiniStop, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate : HashMap::new(), + non_mandate : HashMap::from([ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.phone.number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.number".to_string(), + display_name: "phone".to_string(), + field_type: enums::FieldType::UserPhoneNumber, + value: None, + } + ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), + ] + ), + common : HashMap::new(), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::FamilyMart, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate : HashMap::new(), + non_mandate : HashMap::from([ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.phone.number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.number".to_string(), + display_name: "phone".to_string(), + field_type: enums::FieldType::UserPhoneNumber, + value: None, + } + ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), + ] + ), + common : HashMap::new(), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::Seicomart, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate : HashMap::new(), + non_mandate : HashMap::from([ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.phone.number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.number".to_string(), + display_name: "phone".to_string(), + field_type: enums::FieldType::UserPhoneNumber, + value: None, + } + ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), + ] + ), + common : HashMap::new(), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::PayEasy, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate : HashMap::new(), + non_mandate : HashMap::from([ + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.phone.number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.number".to_string(), + display_name: "phone".to_string(), + field_type: enums::FieldType::UserPhoneNumber, + value: None, + } + ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), + ] + ), + common : HashMap::new(), + } + ) + ]), + }, + ), + ])), + ), + ( + enums::PaymentMethod::Upi, + PaymentMethodType(HashMap::from([ + ( + enums::PaymentMethodType::UpiCollect, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Razorpay, + RequiredFieldFinal { + mandate : HashMap::new(), + non_mandate : HashMap::new(), + common : HashMap::from([ + ( + "payment_method_data.upi.upi_collect.vpa_id".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.upi.upi_collect.vpa_id".to_string(), + display_name: "vpa_id".to_string(), + field_type: enums::FieldType::UserVpaId, + value: None, + } + ), + ]), + } + ), + ]), + }, + ), + ])), + ), + ( + enums::PaymentMethod::BankDebit, + PaymentMethodType(HashMap::from([( + enums::PaymentMethodType::Ach, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "owner_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "payment_method_data.bank_debit.ach_bank_debit.account_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_debit.ach_bank_debit.account_number".to_string(), + display_name: "bank_account_number".to_string(), + field_type: enums::FieldType::UserBankAccountNumber, + value: None, + } + ), + ( + "payment_method_data.bank_debit.ach_bank_debit.routing_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_debit.ach_bank_debit.routing_number".to_string(), + display_name: "bank_routing_number".to_string(), + field_type: enums::FieldType::UserBankRoutingNumber, + value: None, + } + ) + ]), + }), + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "owner_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + }), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "owner_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "payment_method_data.bank_debit.ach_bank_debit.account_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_debit.ach_bank_debit.account_number".to_string(), + display_name: "bank_account_number".to_string(), + field_type: enums::FieldType::UserBankAccountNumber, + value: None, + } + ), + ( + "payment_method_data.bank_debit.ach_bank_debit.routing_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_debit.ach_bank_debit.routing_number".to_string(), + display_name: "bank_routing_number".to_string(), + field_type: enums::FieldType::UserBankRoutingNumber, + value: None, + } + ) + ]), + }) + ] + )} + ), + ( + enums::PaymentMethodType::Sepa, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "owner_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "payment_method_data.bank_debit.sepa_bank_debit.iban".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_debit.sepa_bank_debit.iban".to_string(), + display_name: "iban".to_string(), + field_type: enums::FieldType::UserIban, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ) + ]), + } + ), + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "owner_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + }), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "owner_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "payment_method_data.bank_debit.sepa_bank_debit.iban".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_debit.sepa_bank_debit.iban".to_string(), + display_name: "iban".to_string(), + field_type: enums::FieldType::UserIban, + value: None, + } + ) + ]), + } + ), + ( + enums::Connector::Deutschebank, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "owner_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + }), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "owner_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "payment_method_data.bank_debit.sepa_bank_debit.iban".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_debit.sepa_bank_debit.iban".to_string(), + display_name: "iban".to_string(), + field_type: enums::FieldType::UserIban, + value: None, + } + ) + ]), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::Bacs, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "payment_method_data.bank_debit.bacs_bank_debit.account_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_debit.bacs_bank_debit.account_number".to_string(), + display_name: "bank_account_number".to_string(), + field_type: enums::FieldType::UserBankAccountNumber, + value: None, + } + ), + ( + "payment_method_data.bank_debit.bacs_bank_debit.sort_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_debit.bacs_bank_debit.sort_code".to_string(), + display_name: "bank_sort_code".to_string(), + field_type: enums::FieldType::UserBankSortCode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry { + options: vec!["UK".to_string()], + }, + value: None, + }, + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + }, + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + }, + ) + ]), + } + ), + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "owner_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + }), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "owner_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "payment_method_data.bank_debit.bacs_bank_debit.account_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_debit.bacs_bank_debit.account_number".to_string(), + display_name: "bank_account_number".to_string(), + field_type: enums::FieldType::UserBankAccountNumber, + value: None, + } + ), + ( + "payment_method_data.bank_debit.bacs_bank_debit.sort_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_debit.bacs_bank_debit.sort_code".to_string(), + display_name: "bank_sort_code".to_string(), + field_type: enums::FieldType::UserBankSortCode, + value: None, + } + ) + ]), + }) + ]), + }, + ), + ( + enums::PaymentMethodType::Becs, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "owner_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "payment_method_data.bank_debit.becs_bank_debit.account_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_debit.becs_bank_debit.account_number".to_string(), + display_name: "bank_account_number".to_string(), + field_type: enums::FieldType::UserBankAccountNumber, + value: None, + } + ), + ( + "payment_method_data.bank_debit.becs_bank_debit.bsb_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_debit.becs_bank_debit.bsb_number".to_string(), + display_name: "bsb_number".to_string(), + field_type: enums::FieldType::UserBsbNumber, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ) + ]), + } + ), + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "owner_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + }), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "owner_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "payment_method_data.bank_debit.becs_bank_debit.account_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_debit.becs_bank_debit.account_number".to_string(), + display_name: "bank_account_number".to_string(), + field_type: enums::FieldType::UserBankAccountNumber, + value: None, + } + ), + ( + "payment_method_data.bank_debit.becs_bank_debit.sort_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_debit.becs_bank_debit.sort_code".to_string(), + display_name: "bank_sort_code".to_string(), + field_type: enums::FieldType::UserBankSortCode, + value: None, + } + ) + ]), + }) + ]), + }, + ), + ]))), + ( + enums::PaymentMethod::BankTransfer, + PaymentMethodType(HashMap::from([( + enums::PaymentMethodType::Multibanco, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ) + ]), + common: HashMap::new(), + } + ), + ])}), + (enums::PaymentMethodType::LocalBankTransfer, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Zsl, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "CN".to_string(), + ] + }, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + }, + ), + ]), + common: HashMap::new(), + } + ), + ])}), + (enums::PaymentMethodType::Ach, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from([ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ) + ]) + } + ), + ])}), + (enums::PaymentMethodType::Pix, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Itaubank, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.bank_transfer.pix.pix_key".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_transfer.pix.pix_key".to_string(), + display_name: "pix_key".to_string(), + field_type: enums::FieldType::UserPixKey, + value: None, + } + ), + ( + "payment_method_data.bank_transfer.pix.cnpj".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_transfer.pix.cnpj".to_string(), + display_name: "cnpj".to_string(), + field_type: enums::FieldType::UserCnpj, + value: None, + } + ), + ( + "payment_method_data.bank_transfer.pix.cpf".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.bank_transfer.pix.cpf".to_string(), + display_name: "cpf".to_string(), + field_type: enums::FieldType::UserCpf, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ] + ), + } + ), + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ])}), + ( + enums::PaymentMethodType::PermataBankTransfer, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate : HashMap::new(), + non_mandate : HashMap::from([ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ) + ]), + common : HashMap::new(), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::BcaBankTransfer, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate : HashMap::new(), + non_mandate : HashMap::from([ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ) + ]), + common : HashMap::new(), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::BniVa, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate : HashMap::new(), + non_mandate : HashMap::from([ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ) + ]), + common : HashMap::new(), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::BriVa, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate : HashMap::new(), + non_mandate : HashMap::from([ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ) + ]), + common : HashMap::new(), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::CimbVa, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate : HashMap::new(), + non_mandate : HashMap::from([ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ) + ]), + common : HashMap::new(), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::DanamonVa, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate : HashMap::new(), + non_mandate : HashMap::from([ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ) + ]), + common : HashMap::new(), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::MandiriVa, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate : HashMap::new(), + non_mandate : HashMap::from([ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ) + ]), + common : HashMap::new(), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::Sepa, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate : HashMap::new(), + non_mandate : HashMap::new(), + common : HashMap::from([ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry { + options: vec![ + "BE".to_string(), + "DE".to_string(), + "ES".to_string(), + "FR".to_string(), + "IE".to_string(), + "NL".to_string(), + ], + }, + value: None, + }, + ), + ]), + } + ) + ]), + }, + ), + ( + enums::PaymentMethodType::Bacs, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate : HashMap::new(), + non_mandate : HashMap::new(), + common : HashMap::from([ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ) + ]), + } + ) + ]), + }, + ), + ]))), + ( + enums::PaymentMethod::GiftCard, + PaymentMethodType(HashMap::from([ + ( + enums::PaymentMethodType::PaySafeCard, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ]), + }, + ), + ( + enums::PaymentMethodType::Givex, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from([ + + ( + "payment_method_data.gift_card.number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.gift_card.number".to_string(), + display_name: "gift_card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.gift_card.cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.gift_card.cvc".to_string(), + display_name: "gift_card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ]), + common: HashMap::new(), + } + ), + ]), + }, + ), + ])) + ), + ( + enums::PaymentMethod::CardRedirect, + PaymentMethodType(HashMap::from([ + ( + enums::PaymentMethodType::Benefit, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "first_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "last_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.phone.number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.number".to_string(), + display_name: "phone".to_string(), + field_type: enums::FieldType::UserPhoneNumber, + value: None, + } + ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ) + ] + ), + common: HashMap::new(), + } + ), + ]), + }, + ), + ( + enums::PaymentMethodType::Knet, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "first_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "last_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.phone.number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.number".to_string(), + display_name: "phone".to_string(), + field_type: enums::FieldType::UserPhoneNumber, + value: None, + } + ), + ( + "billing.phone.country_code".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.phone.country_code".to_string(), + display_name: "dialing_code".to_string(), + field_type: enums::FieldType::UserPhoneNumberCountryCode, + value: None, + } + ), + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ) + ] + ), + common: HashMap::new(), + } + ), + ]), + }, + ), + ( + enums::PaymentMethodType::MomoAtm, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Adyen, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ]), + }, + ) + ])) + ), + ( + enums::PaymentMethod::MobilePayment, + PaymentMethodType(HashMap::from([ + ( + enums::PaymentMethodType::DirectCarrierBilling, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Digitalvirgo, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.mobile_payment.direct_carrier_billing.msisdn".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.mobile_payment.direct_carrier_billing.msisdn".to_string(), + display_name: "mobile_number".to_string(), + field_type: enums::FieldType::UserMsisdn, + value: None, + } + ), + ( + "payment_method_data.mobile_payment.direct_carrier_billing.client_uid".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.mobile_payment.direct_carrier_billing.client_uid".to_string(), + display_name: "client_identifier".to_string(), + field_type: enums::FieldType::UserClientIdentifier, + value: None, + } + ), + ( + "order_details.0.product_name".to_string(), + RequiredFieldInfo { + required_field: "order_details.0.product_name".to_string(), + display_name: "product_name".to_string(), + field_type: enums::FieldType::OrderDetailsProductName, + value: None, + } + ), + ] + ), + } + ), + ]) + } + ) + ])) + ) + ])) + } +} + +pub fn get_worldpay_billing_required_fields() -> HashMap { + HashMap::from([ + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + }, + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry { + options: vec![ + "AF".to_string(), + "AU".to_string(), + "AW".to_string(), + "AZ".to_string(), + "BS".to_string(), + "BH".to_string(), + "BD".to_string(), + "BB".to_string(), + "BZ".to_string(), + "BM".to_string(), + "BT".to_string(), + "BO".to_string(), + "BA".to_string(), + "BW".to_string(), + "BR".to_string(), + "BN".to_string(), + "BG".to_string(), + "BI".to_string(), + "KH".to_string(), + "CA".to_string(), + "CV".to_string(), + "KY".to_string(), + "CL".to_string(), + "CO".to_string(), + "KM".to_string(), + "CD".to_string(), + "CR".to_string(), + "CZ".to_string(), + "DZ".to_string(), + "DK".to_string(), + "DJ".to_string(), + "ST".to_string(), + "DO".to_string(), + "EC".to_string(), + "EG".to_string(), + "SV".to_string(), + "ER".to_string(), + "ET".to_string(), + "FK".to_string(), + "FJ".to_string(), + "GM".to_string(), + "GE".to_string(), + "GH".to_string(), + "GI".to_string(), + "GT".to_string(), + "GN".to_string(), + "GY".to_string(), + "HT".to_string(), + "HN".to_string(), + "HK".to_string(), + "HU".to_string(), + "IS".to_string(), + "IN".to_string(), + "ID".to_string(), + "IR".to_string(), + "IQ".to_string(), + "IE".to_string(), + "IL".to_string(), + "IT".to_string(), + "JM".to_string(), + "JP".to_string(), + "JO".to_string(), + "KZ".to_string(), + "KE".to_string(), + "KW".to_string(), + "LA".to_string(), + "LB".to_string(), + "LS".to_string(), + "LR".to_string(), + "LY".to_string(), + "LT".to_string(), + "MO".to_string(), + "MK".to_string(), + "MG".to_string(), + "MW".to_string(), + "MY".to_string(), + "MV".to_string(), + "MR".to_string(), + "MU".to_string(), + "MX".to_string(), + "MD".to_string(), + "MN".to_string(), + "MA".to_string(), + "MZ".to_string(), + "MM".to_string(), + "NA".to_string(), + "NZ".to_string(), + "NI".to_string(), + "NG".to_string(), + "KP".to_string(), + "NO".to_string(), + "AR".to_string(), + "PK".to_string(), + "PG".to_string(), + "PY".to_string(), + "PE".to_string(), + "UY".to_string(), + "PH".to_string(), + "PL".to_string(), + "GB".to_string(), + "QA".to_string(), + "OM".to_string(), + "RO".to_string(), + "RU".to_string(), + "RW".to_string(), + "WS".to_string(), + "SG".to_string(), + "ST".to_string(), + "ZA".to_string(), + "KR".to_string(), + "LK".to_string(), + "SH".to_string(), + "SD".to_string(), + "SR".to_string(), + "SZ".to_string(), + "SE".to_string(), + "CH".to_string(), + "SY".to_string(), + "TW".to_string(), + "TJ".to_string(), + "TZ".to_string(), + "TH".to_string(), + "TT".to_string(), + "TN".to_string(), + "TR".to_string(), + "UG".to_string(), + "UA".to_string(), + "US".to_string(), + "UZ".to_string(), + "VU".to_string(), + "VE".to_string(), + "VN".to_string(), + "ZM".to_string(), + "ZW".to_string(), + ], + }, + value: None, + }, + ), + ]) +} diff --git a/crates/router/src/configs/secrets_transformers.rs b/crates/router/src/configs/secrets_transformers.rs index 0f25477802ce..7b74aca76472 100644 --- a/crates/router/src/configs/secrets_transformers.rs +++ b/crates/router/src/configs/secrets_transformers.rs @@ -178,6 +178,27 @@ impl SecretsHandler for settings::ApplePayDecryptConfig { } } +#[async_trait::async_trait] +impl SecretsHandler for settings::PazeDecryptConfig { + async fn convert_to_raw_secret( + value: SecretStateContainer, + secret_management_client: &dyn SecretManagementInterface, + ) -> CustomResult, SecretsManagementError> { + let paze_decrypt_keys = value.get_inner(); + + let (paze_private_key, paze_private_key_passphrase) = tokio::try_join!( + secret_management_client.get_secret(paze_decrypt_keys.paze_private_key.clone()), + secret_management_client + .get_secret(paze_decrypt_keys.paze_private_key_passphrase.clone()), + )?; + + Ok(value.transition_state(|_| Self { + paze_private_key, + paze_private_key_passphrase, + })) + } +} + #[async_trait::async_trait] impl SecretsHandler for settings::ApplepayMerchantConfigs { async fn convert_to_raw_secret( @@ -388,6 +409,17 @@ pub(crate) async fn fetch_raw_secrets( .await .expect("Failed to decrypt applepay decrypt configs"); + #[allow(clippy::expect_used)] + let paze_decrypt_keys = if let Some(paze_keys) = conf.paze_decrypt_keys { + Some( + settings::PazeDecryptConfig::convert_to_raw_secret(paze_keys, secret_management_client) + .await + .expect("Failed to decrypt paze decrypt configs"), + ) + } else { + None + }; + #[allow(clippy::expect_used)] let applepay_merchant_configs = settings::ApplepayMerchantConfigs::convert_to_raw_secret( conf.applepay_merchant_configs, @@ -479,6 +511,7 @@ pub(crate) async fn fetch_raw_secrets( #[cfg(feature = "payouts")] payouts: conf.payouts, applepay_decrypt_keys, + paze_decrypt_keys, multiple_api_version_supported_connectors: conf.multiple_api_version_supported_connectors, applepay_merchant_configs, lock_settings: conf.lock_settings, @@ -512,5 +545,7 @@ pub(crate) async fn fetch_raw_secrets( .network_tokenization_supported_card_networks, network_tokenization_service, network_tokenization_supported_connectors: conf.network_tokenization_supported_connectors, + theme: conf.theme, + platform: conf.platform, } } diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index 1124423714bf..ad5d9e89aaae 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -6,7 +6,7 @@ use std::{ #[cfg(feature = "olap")] use analytics::{opensearch::OpenSearchConfig, ReportConfig}; use api_models::{enums, payment_methods::RequiredFieldInfo}; -use common_utils::ext_traits::ConfigExt; +use common_utils::{ext_traits::ConfigExt, id_type, types::theme::EmailThemeConfig}; use config::{Environment, File}; use error_stack::ResultExt; #[cfg(feature = "email")] @@ -96,6 +96,7 @@ pub struct Settings { pub payouts: Payouts, pub payout_method_filters: ConnectorFilters, pub applepay_decrypt_keys: SecretStateContainer, + pub paze_decrypt_keys: Option>, pub multiple_api_version_supported_connectors: MultipleApiVersionSupportedConnectors, pub applepay_merchant_configs: SecretStateContainer, pub lock_settings: LockSettings, @@ -127,6 +128,13 @@ pub struct Settings { pub network_tokenization_supported_card_networks: NetworkTokenizationSupportedCardNetworks, pub network_tokenization_service: Option>, pub network_tokenization_supported_connectors: NetworkTokenizationSupportedConnectors, + pub theme: ThemeSettings, + pub platform: Platform, +} + +#[derive(Debug, Deserialize, Clone, Default)] +pub struct Platform { + pub enabled: bool, } #[derive(Debug, Deserialize, Clone, Default)] @@ -137,13 +145,17 @@ pub struct Multitenancy { } impl Multitenancy { - pub fn get_tenants(&self) -> &HashMap { + pub fn get_tenants(&self) -> &HashMap { &self.tenants.0 } - pub fn get_tenant_names(&self) -> Vec { - self.tenants.0.keys().cloned().collect() + pub fn get_tenant_ids(&self) -> Vec { + self.tenants + .0 + .values() + .map(|tenant| tenant.tenant_id.clone()) + .collect() } - pub fn get_tenant(&self, tenant_id: &str) -> Option<&Tenant> { + pub fn get_tenant(&self, tenant_id: &id_type::TenantId) -> Option<&Tenant> { self.tenants.0.get(tenant_id) } } @@ -153,17 +165,22 @@ pub struct DecisionConfig { pub base_url: String, } -#[derive(Debug, Deserialize, Clone, Default)] -#[serde(transparent)] -pub struct TenantConfig(pub HashMap); +#[derive(Debug, Clone, Default)] +pub struct TenantConfig(pub HashMap); -#[derive(Debug, Deserialize, Clone, Default)] +#[derive(Debug, Clone)] pub struct Tenant { - pub name: String, + pub tenant_id: id_type::TenantId, pub base_url: String, pub schema: String, pub redis_key_prefix: String, pub clickhouse_database: String, + pub user: TenantUserConfig, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct TenantUserConfig { + pub control_center_url: String, } impl storage_impl::config::TenantConfig for Tenant { @@ -552,6 +569,8 @@ pub struct UserSettings { pub two_factor_auth_expiry_in_secs: i64, pub totp_issuer_name: String, pub base_url: String, + pub force_two_factor_auth: bool, + pub force_cookies: bool, } #[derive(Debug, Deserialize, Clone)] @@ -723,6 +742,12 @@ pub struct ApplePayDecryptConfig { pub apple_pay_merchant_cert_key: Secret, } +#[derive(Debug, Deserialize, Clone, Default)] +pub struct PazeDecryptConfig { + pub paze_private_key: Secret, + pub paze_private_key_passphrase: Secret, +} + #[derive(Debug, Deserialize, Clone, Default)] #[serde(default)] pub struct LockerBasedRecipientConnectorList { @@ -732,8 +757,7 @@ pub struct LockerBasedRecipientConnectorList { #[derive(Debug, Deserialize, Clone, Default)] pub struct ConnectorRequestReferenceIdConfig { - pub merchant_ids_send_payment_id_as_connector_request_id: - HashSet, + pub merchant_ids_send_payment_id_as_connector_request_id: HashSet, } #[derive(Debug, Deserialize, Clone, Default)] @@ -864,7 +888,21 @@ impl Settings { .map(|x| x.get_inner().validate()) .transpose()?; + self.paze_decrypt_keys + .as_ref() + .map(|x| x.get_inner().validate()) + .transpose()?; + self.key_manager.get_inner().validate()?; + #[cfg(feature = "email")] + self.email + .validate() + .map_err(|err| ApplicationError::InvalidConfigurationValueError(err.into()))?; + + self.theme + .storage + .validate() + .map_err(|err| ApplicationError::InvalidConfigurationValueError(err.to_string()))?; Ok(()) } @@ -950,7 +988,7 @@ pub struct ServerTls { #[cfg(feature = "v2")] #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] pub struct CellInformation { - pub id: common_utils::id_type::CellId, + pub id: id_type::CellId, } #[cfg(feature = "v2")] @@ -961,12 +999,18 @@ impl Default for CellInformation { // around the time of deserializing application settings. // And a panic at application startup is considered acceptable. #[allow(clippy::expect_used)] - let cell_id = common_utils::id_type::CellId::from_string("defid") - .expect("Failed to create a default for Cell Id"); + let cell_id = + id_type::CellId::from_string("defid").expect("Failed to create a default for Cell Id"); Self { id: cell_id } } } +#[derive(Debug, Deserialize, Clone, Default)] +pub struct ThemeSettings { + pub storage: FileStorageConfig, + pub email_config: EmailThemeConfig, +} + fn deserialize_hashmap_inner( value: HashMap, ) -> Result>, String> @@ -1090,6 +1134,40 @@ where })? } +impl<'de> Deserialize<'de> for TenantConfig { + fn deserialize>(deserializer: D) -> Result { + #[derive(Deserialize)] + struct Inner { + base_url: String, + schema: String, + redis_key_prefix: String, + clickhouse_database: String, + user: TenantUserConfig, + } + + let hashmap = >::deserialize(deserializer)?; + + Ok(Self( + hashmap + .into_iter() + .map(|(key, value)| { + ( + key.clone(), + Tenant { + tenant_id: key, + base_url: value.base_url, + schema: value.schema, + redis_key_prefix: value.redis_key_prefix, + clickhouse_database: value.clickhouse_database, + user: value.user, + }, + ) + }) + .collect(), + )) + } +} + #[cfg(test)] mod hashmap_deserialization_test { #![allow(clippy::unwrap_used)] diff --git a/crates/router/src/configs/validations.rs b/crates/router/src/configs/validations.rs index f109fe3f773a..7040998ccf01 100644 --- a/crates/router/src/configs/validations.rs +++ b/crates/router/src/configs/validations.rs @@ -236,6 +236,27 @@ impl super::settings::NetworkTokenizationService { } } +impl super::settings::PazeDecryptConfig { + pub fn validate(&self) -> Result<(), ApplicationError> { + use common_utils::fp_utils::when; + + when(self.paze_private_key.is_default_or_empty(), || { + Err(ApplicationError::InvalidConfigurationValueError( + "paze_private_key must not be empty".into(), + )) + })?; + + when( + self.paze_private_key_passphrase.is_default_or_empty(), + || { + Err(ApplicationError::InvalidConfigurationValueError( + "paze_private_key_passphrase must not be empty".into(), + )) + }, + ) + } +} + impl super::settings::KeyManagerConfig { pub fn validate(&self) -> Result<(), ApplicationError> { use common_utils::fp_utils::when; diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs index 390d5de1513a..0da0e711fbca 100644 --- a/crates/router/src/connector.rs +++ b/crates/router/src/connector.rs @@ -1,49 +1,31 @@ pub mod aci; pub mod adyen; pub mod adyenplatform; -pub mod airwallex; pub mod authorizedotnet; -pub mod bamboraapac; pub mod bankofamerica; -pub mod billwerk; -pub mod bluesnap; -pub mod boku; pub mod braintree; pub mod checkout; pub mod cybersource; -pub mod datatrans; #[cfg(feature = "dummy_connector")] pub mod dummyconnector; pub mod ebanx; -pub mod forte; pub mod globalpay; -pub mod gocardless; pub mod gpayments; pub mod iatapay; pub mod itaubank; pub mod klarna; pub mod mifinity; -pub mod multisafepay; pub mod netcetera; -pub mod nexinets; pub mod nmi; pub mod noon; pub mod nuvei; pub mod opayo; pub mod opennode; -pub mod paybox; -pub mod payeezy; pub mod payme; pub mod payone; pub mod paypal; -pub mod payu; -pub mod placetopay; pub mod plaid; -pub mod prophetpay; -pub mod rapyd; -pub mod razorpay; pub mod riskified; -pub mod shift4; pub mod signifyd; pub mod stripe; pub mod threedsecureio; @@ -52,33 +34,38 @@ pub mod utils; pub mod wellsfargo; pub mod wellsfargopayout; pub mod wise; -pub mod worldpay; -pub mod zen; -pub mod zsl; pub use hyperswitch_connectors::connectors::{ - bambora, bambora::Bambora, bitpay, bitpay::Bitpay, cashtocode, cashtocode::Cashtocode, - coinbase, coinbase::Coinbase, cryptopay, cryptopay::Cryptopay, deutschebank, - deutschebank::Deutschebank, dlocal, dlocal::Dlocal, fiserv, fiserv::Fiserv, fiservemea, - fiservemea::Fiservemea, fiuu, fiuu::Fiuu, globepay, globepay::Globepay, helcim, helcim::Helcim, - mollie, mollie::Mollie, nexixpay, nexixpay::Nexixpay, novalnet, novalnet::Novalnet, powertranz, - powertranz::Powertranz, square, square::Square, stax, stax::Stax, taxjar, taxjar::Taxjar, - thunes, thunes::Thunes, tsys, tsys::Tsys, volt, volt::Volt, worldline, worldline::Worldline, + airwallex, airwallex::Airwallex, amazonpay, amazonpay::Amazonpay, bambora, bambora::Bambora, + bamboraapac, bamboraapac::Bamboraapac, billwerk, billwerk::Billwerk, bitpay, bitpay::Bitpay, + bluesnap, bluesnap::Bluesnap, boku, boku::Boku, cashtocode, cashtocode::Cashtocode, coinbase, + coinbase::Coinbase, cryptopay, cryptopay::Cryptopay, ctp_mastercard, + ctp_mastercard::CtpMastercard, datatrans, datatrans::Datatrans, deutschebank, + deutschebank::Deutschebank, digitalvirgo, digitalvirgo::Digitalvirgo, dlocal, dlocal::Dlocal, + elavon, elavon::Elavon, fiserv, fiserv::Fiserv, fiservemea, fiservemea::Fiservemea, fiuu, + fiuu::Fiuu, forte, forte::Forte, globepay, globepay::Globepay, gocardless, + gocardless::Gocardless, helcim, helcim::Helcim, inespay, inespay::Inespay, jpmorgan, + jpmorgan::Jpmorgan, mollie, mollie::Mollie, multisafepay, multisafepay::Multisafepay, nexinets, + nexinets::Nexinets, nexixpay, nexixpay::Nexixpay, nomupay, nomupay::Nomupay, novalnet, + novalnet::Novalnet, paybox, paybox::Paybox, payeezy, payeezy::Payeezy, payu, payu::Payu, + placetopay, placetopay::Placetopay, powertranz, powertranz::Powertranz, prophetpay, + prophetpay::Prophetpay, rapyd, rapyd::Rapyd, razorpay, razorpay::Razorpay, redsys, + redsys::Redsys, shift4, shift4::Shift4, square, square::Square, stax, stax::Stax, taxjar, + taxjar::Taxjar, thunes, thunes::Thunes, tsys, tsys::Tsys, unified_authentication_service, + unified_authentication_service::UnifiedAuthenticationService, volt, volt::Volt, worldline, + worldline::Worldline, worldpay, worldpay::Worldpay, xendit, xendit::Xendit, zen, zen::Zen, zsl, + zsl::Zsl, }; #[cfg(feature = "dummy_connector")] pub use self::dummyconnector::DummyConnector; pub use self::{ - aci::Aci, adyen::Adyen, adyenplatform::Adyenplatform, airwallex::Airwallex, - authorizedotnet::Authorizedotnet, bamboraapac::Bamboraapac, bankofamerica::Bankofamerica, - billwerk::Billwerk, bluesnap::Bluesnap, boku::Boku, braintree::Braintree, checkout::Checkout, - cybersource::Cybersource, datatrans::Datatrans, ebanx::Ebanx, forte::Forte, - globalpay::Globalpay, gocardless::Gocardless, gpayments::Gpayments, iatapay::Iatapay, - itaubank::Itaubank, klarna::Klarna, mifinity::Mifinity, multisafepay::Multisafepay, - netcetera::Netcetera, nexinets::Nexinets, nmi::Nmi, noon::Noon, nuvei::Nuvei, opayo::Opayo, - opennode::Opennode, paybox::Paybox, payeezy::Payeezy, payme::Payme, payone::Payone, - paypal::Paypal, payu::Payu, placetopay::Placetopay, plaid::Plaid, prophetpay::Prophetpay, - rapyd::Rapyd, razorpay::Razorpay, riskified::Riskified, shift4::Shift4, signifyd::Signifyd, + aci::Aci, adyen::Adyen, adyenplatform::Adyenplatform, authorizedotnet::Authorizedotnet, + bankofamerica::Bankofamerica, braintree::Braintree, checkout::Checkout, + cybersource::Cybersource, ebanx::Ebanx, globalpay::Globalpay, gpayments::Gpayments, + iatapay::Iatapay, itaubank::Itaubank, klarna::Klarna, mifinity::Mifinity, netcetera::Netcetera, + nmi::Nmi, noon::Noon, nuvei::Nuvei, opayo::Opayo, opennode::Opennode, payme::Payme, + payone::Payone, paypal::Paypal, plaid::Plaid, riskified::Riskified, signifyd::Signifyd, stripe::Stripe, threedsecureio::Threedsecureio, trustpay::Trustpay, wellsfargo::Wellsfargo, - wellsfargopayout::Wellsfargopayout, wise::Wise, worldpay::Worldpay, zen::Zen, zsl::Zsl, + wellsfargopayout::Wellsfargopayout, wise::Wise, }; diff --git a/crates/router/src/connector/aci.rs b/crates/router/src/connector/aci.rs index 6f800194a476..c59c0ce07556 100644 --- a/crates/router/src/connector/aci.rs +++ b/crates/router/src/connector/aci.rs @@ -6,6 +6,7 @@ use common_utils::{ types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector}, }; use error_stack::{report, ResultExt}; +use hyperswitch_interfaces::api::ConnectorSpecifications; use masking::PeekInterface; use transformers as aci; @@ -616,3 +617,5 @@ impl api::IncomingWebhook for Aci { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for Aci {} diff --git a/crates/router/src/connector/aci/transformers.rs b/crates/router/src/connector/aci/transformers.rs index 46f312b38f26..3e25799c02e6 100644 --- a/crates/router/src/connector/aci/transformers.rs +++ b/crates/router/src/connector/aci/transformers.rs @@ -120,6 +120,7 @@ impl TryFrom<(&domain::WalletData, &types::PaymentsAuthorizeRouterData)> for Pay | domain::WalletData::MobilePayRedirect(_) | domain::WalletData::PaypalRedirect(_) | domain::WalletData::PaypalSdk(_) + | domain::WalletData::Paze(_) | domain::WalletData::SamsungPay(_) | domain::WalletData::TwintRedirect { .. } | domain::WalletData::VippsRedirect { .. } @@ -433,13 +434,15 @@ impl TryFrom<&AciRouterData<&types::PaymentsAuthorizeRouterData>> for AciPayment | domain::PaymentMethodData::BankTransfer(_) | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::CardRedirect(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Aci"), ))? @@ -747,6 +750,7 @@ impl connector_mandate_id: Some(id.expose()), payment_method_id: None, mandate_metadata: None, + connector_mandate_request_reference_id: None, }); Ok(Self { @@ -761,8 +765,8 @@ impl }, response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()), - redirection_data, - mandate_reference, + redirection_data: Box::new(redirection_data), + mandate_reference: Box::new(mandate_reference), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(item.response.id), diff --git a/crates/router/src/connector/adyen.rs b/crates/router/src/connector/adyen.rs index 810551e0cb7b..b9ab7ea87e0f 100644 --- a/crates/router/src/connector/adyen.rs +++ b/crates/router/src/connector/adyen.rs @@ -7,6 +7,7 @@ use common_utils::{ }; use diesel_models::{enums as storage_enums, enums}; use error_stack::{report, ResultExt}; +use hyperswitch_interfaces::webhooks::IncomingWebhookFlowError; use masking::{ExposeInterface, Secret}; use ring::hmac; use router_env::{instrument, tracing}; @@ -26,7 +27,7 @@ use crate::{ services::{ self, request::{self, Mask}, - ConnectorValidation, + ConnectorSpecifications, ConnectorValidation, }, types::{ self, @@ -97,9 +98,10 @@ impl ConnectorCommon for Adyen { } impl ConnectorValidation for Adyen { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); @@ -119,6 +121,7 @@ impl ConnectorValidation for Adyen { | PaymentMethodType::Venmo | PaymentMethodType::Paypal => match capture_method { enums::CaptureMethod::Automatic + | enums::CaptureMethod::SequentialAutomatic | enums::CaptureMethod::Manual | enums::CaptureMethod::ManualMultiple => Ok(()), enums::CaptureMethod::Scheduled => { @@ -131,13 +134,16 @@ impl ConnectorValidation for Adyen { }, PaymentMethodType::Ach | PaymentMethodType::SamsungPay + | PaymentMethodType::Paze | PaymentMethodType::Alma | PaymentMethodType::Bacs | PaymentMethodType::Givex | PaymentMethodType::Klarna | PaymentMethodType::Twint | PaymentMethodType::Walley => match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => { capture_method_not_supported!( connector, @@ -196,7 +202,9 @@ impl ConnectorValidation for Adyen { | PaymentMethodType::OpenBankingUk | PaymentMethodType::OnlineBankingCzechRepublic | PaymentMethodType::PermataBankTransfer => match capture_method { - enums::CaptureMethod::Automatic => Ok(()), + enums::CaptureMethod::Automatic | enums::CaptureMethod::SequentialAutomatic => { + Ok(()) + } enums::CaptureMethod::Manual | enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => { @@ -208,6 +216,7 @@ impl ConnectorValidation for Adyen { } }, PaymentMethodType::CardRedirect + | PaymentMethodType::DirectCarrierBilling | PaymentMethodType::Fps | PaymentMethodType::DuitNow | PaymentMethodType::Interac @@ -236,6 +245,7 @@ impl ConnectorValidation for Adyen { }, None => match capture_method { enums::CaptureMethod::Automatic + | enums::CaptureMethod::SequentialAutomatic | enums::CaptureMethod::Manual | enums::CaptureMethod::ManualMultiple => Ok(()), enums::CaptureMethod::Scheduled => { @@ -1878,6 +1888,7 @@ impl api::IncomingWebhook for Adyen { fn get_webhook_api_response( &self, _request: &api::IncomingWebhookRequestDetails<'_>, + _error_kind: Option, ) -> CustomResult, errors::ConnectorError> { Ok(services::api::ApplicationResponse::TextPlain( @@ -1893,7 +1904,7 @@ impl api::IncomingWebhook for Adyen { .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; Ok(api::disputes::DisputePayload { amount: notif.amount.value.to_string(), - currency: notif.amount.currency.to_string(), + currency: notif.amount.currency, dispute_stage: api_models::enums::DisputeStage::from(notif.event_code.clone()), connector_dispute_id: notif.psp_reference, connector_reason: notif.reason, @@ -1904,6 +1915,48 @@ impl api::IncomingWebhook for Adyen { updated_at: notif.event_date, }) } + + fn get_mandate_details( + &self, + request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult< + Option, + errors::ConnectorError, + > { + let notif = get_webhook_object_from_body(request.body) + .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; + let mandate_reference = + notif + .additional_data + .recurring_detail_reference + .map(|mandate_id| { + hyperswitch_domain_models::router_flow_types::ConnectorMandateDetails { + connector_mandate_id: mandate_id.clone(), + } + }); + Ok(mandate_reference) + } + + fn get_network_txn_id( + &self, + request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult< + Option, + errors::ConnectorError, + > { + let notif = get_webhook_object_from_body(request.body) + .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; + let optional_network_txn_id = + notif + .additional_data + .network_tx_reference + .map(|network_txn_id| { + hyperswitch_domain_models::router_flow_types::ConnectorNetworkTxnId::new( + network_txn_id, + ) + }); + Ok(optional_network_txn_id) + } } impl api::Dispute for Adyen {} @@ -2239,3 +2292,5 @@ impl api::FileUpload for Adyen { Ok(()) } } + +impl ConnectorSpecifications for Adyen {} diff --git a/crates/router/src/connector/adyen/transformers.rs b/crates/router/src/connector/adyen/transformers.rs index dfb0963c70a0..394652c33e11 100644 --- a/crates/router/src/connector/adyen/transformers.rs +++ b/crates/router/src/connector/adyen/transformers.rs @@ -14,8 +14,8 @@ use time::{Duration, OffsetDateTime, PrimitiveDateTime}; use crate::{connector::utils::PayoutsData, types::api::payouts, utils::OptionExt}; use crate::{ connector::utils::{ - self, AddressDetailsData, BrowserInformationData, CardData, MandateReferenceData, - PaymentsAuthorizeRequestData, PhoneDetailsData, RouterData, + self, missing_field_err, AddressDetailsData, BrowserInformationData, CardData, + NetworkTokenData, PaymentsAuthorizeRequestData, PhoneDetailsData, RouterData, }, consts, core::errors, @@ -89,6 +89,7 @@ pub enum AuthType { #[default] PreAuth, } +#[serde_with::skip_serializing_none] #[derive(Clone, Default, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AdditionalData { @@ -107,6 +108,7 @@ pub struct AdditionalData { funds_availability: Option, } +#[serde_with::skip_serializing_none] #[derive(Default, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ShopperName { @@ -114,6 +116,7 @@ pub struct ShopperName { last_name: Option>, } +#[serde_with::skip_serializing_none] #[derive(Default, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Address { @@ -122,9 +125,10 @@ pub struct Address { house_number_or_name: Secret, postal_code: Secret, state_or_province: Option>, - street: Secret, + street: Option>, } +#[serde_with::skip_serializing_none] #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct LineItem { @@ -143,6 +147,7 @@ pub struct AdyenPaymentRequest<'a> { amount: Amount, merchant_account: Secret, payment_method: AdyenPaymentMethod<'a>, + mpi_data: Option, reference: String, return_url: String, browser_info: Option, @@ -168,6 +173,16 @@ pub struct AdyenPaymentRequest<'a> { merchant_order_reference: Option, } +#[serde_with::skip_serializing_none] +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +struct AdyenMpiData { + directory_response: String, + authentication_response: String, + token_authentication_verification_value: Secret, + eci: Option, +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] struct AdyenBrowserInfo { @@ -488,7 +503,7 @@ pub struct Amount { } #[derive(Debug, Clone, Serialize)] -#[serde(tag = "type")] +#[serde(untagged)] #[serde(rename_all = "lowercase")] pub enum AdyenPaymentMethod<'a> { AdyenAffirm(Box), @@ -603,6 +618,7 @@ pub enum AdyenPaymentMethod<'a> { #[serde(rename = "econtext_stores")] PayEasy(Box), Pix(Box), + NetworkToken(Box), } #[derive(Debug, Clone, Serialize)] @@ -1076,6 +1092,7 @@ pub struct BlikRedirectionData { blik_code: Secret, } +#[serde_with::skip_serializing_none] #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct BankRedirectionWithIssuer<'a> { @@ -1092,6 +1109,7 @@ pub struct AdyenMandate { stored_payment_method_id: Secret, } +#[serde_with::skip_serializing_none] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AdyenCard { @@ -1112,6 +1130,38 @@ pub enum CardBrand { Visa, MC, Amex, + Argencard, + Bcmc, + Bijcard, + Cabal, + Cartebancaire, + Codensa, + Cup, + Dankort, + Diners, + Discover, + Electron, + Elo, + Forbrugsforeningen, + Hiper, + Hipercard, + Jcb, + Karenmillen, + Laser, + Maestro, + Maestrouk, + Mcalphabankbonus, + Mir, + Naranja, + Oasis, + Rupay, + Shopping, + Solo, + Troy, + Uatp, + Visaalphabankbonus, + Visadankort, + Warehouse, } #[derive(Default, Debug, Serialize, Deserialize)] @@ -1167,6 +1217,20 @@ pub struct AdyenApplePay { apple_pay_token: Secret, } +#[serde_with::skip_serializing_none] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AdyenNetworkTokenData { + #[serde(rename = "type")] + payment_type: PaymentType, + number: CardNumber, + expiry_month: Secret, + expiry_year: Secret, + holder_name: Option>, + brand: Option, //Mandatory for mandate using network_txns_id + network_payment_reference: Option>, +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct DokuBankData { @@ -1175,6 +1239,7 @@ pub struct DokuBankData { shopper_email: Email, } // Refunds Request and Response +#[serde_with::skip_serializing_none] #[derive(Default, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AdyenRefundRequest { @@ -1404,7 +1469,7 @@ pub enum OpenBankingUKIssuer { pub struct AdyenTestBankNames<'a>(&'a str); -impl<'a> TryFrom<&common_enums::BankNames> for AdyenTestBankNames<'a> { +impl TryFrom<&common_enums::BankNames> for AdyenTestBankNames<'_> { type Error = Error; fn try_from(bank: &common_enums::BankNames) -> Result { Ok(match bank { @@ -1457,7 +1522,7 @@ impl<'a> TryFrom<&common_enums::BankNames> for AdyenTestBankNames<'a> { pub struct AdyenBankNames<'a>(&'a str); -impl<'a> TryFrom<&common_enums::BankNames> for AdyenBankNames<'a> { +impl TryFrom<&common_enums::BankNames> for AdyenBankNames<'_> { type Error = Error; fn try_from(bank: &common_enums::BankNames) -> Result { Ok(match bank { @@ -1506,9 +1571,7 @@ impl TryFrom<&types::ConnectorAuthType> for AdyenAuthType { } } -impl<'a> TryFrom<&AdyenRouterData<&types::PaymentsAuthorizeRouterData>> - for AdyenPaymentRequest<'a> -{ +impl TryFrom<&AdyenRouterData<&types::PaymentsAuthorizeRouterData>> for AdyenPaymentRequest<'_> { type Error = Error; fn try_from( item: &AdyenRouterData<&types::PaymentsAuthorizeRouterData>, @@ -1549,14 +1612,18 @@ impl<'a> TryFrom<&AdyenRouterData<&types::PaymentsAuthorizeRouterData>> domain::PaymentMethodData::GiftCard(ref gift_card_data) => { AdyenPaymentRequest::try_from((item, gift_card_data.as_ref())) } + domain::PaymentMethodData::NetworkToken(ref token_data) => { + AdyenPaymentRequest::try_from((item, token_data)) + } domain::PaymentMethodData::Crypto(_) | domain::PaymentMethodData::MandatePayment | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Adyen"), ))? @@ -1566,7 +1633,7 @@ impl<'a> TryFrom<&AdyenRouterData<&types::PaymentsAuthorizeRouterData>> } } -impl<'a> TryFrom<&types::PaymentsPreProcessingRouterData> for AdyenBalanceRequest<'a> { +impl TryFrom<&types::PaymentsPreProcessingRouterData> for AdyenBalanceRequest<'_> { type Error = Error; fn try_from(item: &types::PaymentsPreProcessingRouterData) -> Result { let payment_method = match &item.request.payment_method_data { @@ -1682,19 +1749,25 @@ fn get_additional_data(item: &types::PaymentsAuthorizeRouterData) -> Option additionalData: {} + //returning None, ensures that additionalData key will not be present in the serialized JSON + None + } else { + Some(AdditionalData { + authorisation_type, + manual_capture, + execute_three_d, + network_tx_reference: None, + recurring_detail_reference: None, + recurring_shopper_reference: None, + recurring_processing_model: None, + ..AdditionalData::default() + }) + } } -fn get_channel_type(pm_type: &Option) -> Option { +fn get_channel_type(pm_type: Option) -> Option { pm_type.as_ref().and_then(|pmt| match pmt { storage_enums::PaymentMethodType::GoPay | storage_enums::PaymentMethodType::Vipps => { Some(Channel::Web) @@ -1711,7 +1784,7 @@ fn get_amount_data(item: &AdyenRouterData<&types::PaymentsAuthorizeRouterData>) } pub fn get_address_info( - address: Option<&payments::Address>, + address: Option<&hyperswitch_domain_models::address::Address>, ) -> Option>> { address.and_then(|add| { add.address.as_ref().map( @@ -1722,7 +1795,7 @@ pub fn get_address_info( house_number_or_name: a.get_line1()?.to_owned(), postal_code: a.get_zip()?.to_owned(), state_or_province: a.state.clone(), - street: a.get_line2()?.to_owned(), + street: a.get_optional_line2().to_owned(), }) }, ) @@ -1730,15 +1803,14 @@ pub fn get_address_info( } fn get_line_items(item: &AdyenRouterData<&types::PaymentsAuthorizeRouterData>) -> Vec { - let order_details: Option> = - item.router_data.request.order_details.clone(); + let order_details = item.router_data.request.order_details.clone(); match order_details { Some(od) => od .iter() .enumerate() .map(|(i, data)| LineItem { - amount_including_tax: Some(MinorUnit::new(data.amount)), - amount_excluding_tax: Some(MinorUnit::new(data.amount)), + amount_including_tax: Some(data.amount), + amount_excluding_tax: Some(data.amount), description: Some(data.product_name.clone()), id: Some(format!("Items #{i}")), tax_amount: None, @@ -1774,7 +1846,9 @@ fn get_telephone_number(item: &types::PaymentsAuthorizeRouterData) -> Option) -> Option { +fn get_shopper_name( + address: Option<&hyperswitch_domain_models::address::Address>, +) -> Option { let billing = address.and_then(|billing| billing.address.as_ref()); Some(ShopperName { first_name: billing.and_then(|a| a.first_name.clone()), @@ -1782,7 +1856,9 @@ fn get_shopper_name(address: Option<&payments::Address>) -> Option }) } -fn get_country_code(address: Option<&payments::Address>) -> Option { +fn get_country_code( + address: Option<&hyperswitch_domain_models::address::Address>, +) -> Option { address.and_then(|billing| billing.address.as_ref().and_then(|address| address.country)) } @@ -1818,8 +1894,8 @@ fn build_shopper_reference( }) } -impl<'a> TryFrom<(&domain::BankDebitData, &types::PaymentsAuthorizeRouterData)> - for AdyenPaymentMethod<'a> +impl TryFrom<(&domain::BankDebitData, &types::PaymentsAuthorizeRouterData)> + for AdyenPaymentMethod<'_> { type Error = Error; fn try_from( @@ -1866,8 +1942,8 @@ impl<'a> TryFrom<(&domain::BankDebitData, &types::PaymentsAuthorizeRouterData)> } } -impl<'a> TryFrom<(&domain::VoucherData, &types::PaymentsAuthorizeRouterData)> - for AdyenPaymentMethod<'a> +impl TryFrom<(&domain::VoucherData, &types::PaymentsAuthorizeRouterData)> + for AdyenPaymentMethod<'_> { type Error = Error; fn try_from( @@ -1913,7 +1989,7 @@ impl<'a> TryFrom<(&domain::VoucherData, &types::PaymentsAuthorizeRouterData)> } } -impl<'a> TryFrom<&domain::GiftCardData> for AdyenPaymentMethod<'a> { +impl TryFrom<&domain::GiftCardData> for AdyenPaymentMethod<'_> { type Error = Error; fn try_from(gift_card_data: &domain::GiftCardData) -> Result { match gift_card_data { @@ -1931,7 +2007,23 @@ impl<'a> TryFrom<&domain::GiftCardData> for AdyenPaymentMethod<'a> { } } -impl<'a> TryFrom<(&domain::Card, Option>)> for AdyenPaymentMethod<'a> { +fn get_adyen_card_network(card_network: common_enums::CardNetwork) -> Option { + match card_network { + common_enums::CardNetwork::Visa => Some(CardBrand::Visa), + common_enums::CardNetwork::Mastercard => Some(CardBrand::MC), + common_enums::CardNetwork::CartesBancaires => Some(CardBrand::Cartebancaire), + common_enums::CardNetwork::AmericanExpress => Some(CardBrand::Amex), + common_enums::CardNetwork::JCB => Some(CardBrand::Jcb), + common_enums::CardNetwork::DinersClub => Some(CardBrand::Diners), + common_enums::CardNetwork::Discover => Some(CardBrand::Discover), + common_enums::CardNetwork::UnionPay => Some(CardBrand::Cup), + common_enums::CardNetwork::RuPay => Some(CardBrand::Rupay), + common_enums::CardNetwork::Maestro => Some(CardBrand::Maestro), + common_enums::CardNetwork::Interac => None, + } +} + +impl TryFrom<(&domain::Card, Option>)> for AdyenPaymentMethod<'_> { type Error = Error; fn try_from( (card, card_holder_name): (&domain::Card, Option>), @@ -1943,7 +2035,7 @@ impl<'a> TryFrom<(&domain::Card, Option>)> for AdyenPaymentMethod expiry_year: card.get_expiry_year_4_digit(), cvc: Some(card.card_cvc.clone()), holder_name: card_holder_name, - brand: None, + brand: card.card_network.clone().and_then(get_adyen_card_network), network_payment_reference: None, }; Ok(AdyenPaymentMethod::AdyenCard(Box::new(adyen_card))) @@ -1998,13 +2090,17 @@ impl TryFrom<&utils::CardIssuer> for CardBrand { utils::CardIssuer::AmericanExpress => Ok(Self::Amex), utils::CardIssuer::Master => Ok(Self::MC), utils::CardIssuer::Visa => Ok(Self::Visa), - _ => Err(errors::ConnectorError::NotImplemented("CardBrand".to_string()).into()), + utils::CardIssuer::Maestro => Ok(Self::Maestro), + utils::CardIssuer::Discover => Ok(Self::Discover), + utils::CardIssuer::DinersClub => Ok(Self::Diners), + utils::CardIssuer::JCB => Ok(Self::Jcb), + utils::CardIssuer::CarteBlanche => Ok(Self::Cartebancaire), } } } -impl<'a> TryFrom<(&domain::WalletData, &types::PaymentsAuthorizeRouterData)> - for AdyenPaymentMethod<'a> +impl TryFrom<(&domain::WalletData, &types::PaymentsAuthorizeRouterData)> + for AdyenPaymentMethod<'_> { type Error = Error; fn try_from( @@ -2087,6 +2183,29 @@ impl<'a> TryFrom<(&domain::WalletData, &types::PaymentsAuthorizeRouterData)> }; Ok(AdyenPaymentMethod::SamsungPay(Box::new(data))) } + domain::WalletData::Paze(_) => match item.payment_method_token.clone() { + Some(types::PaymentMethodToken::PazeDecrypt(paze_decrypted_data)) => { + let data = AdyenCard { + payment_type: PaymentType::NetworkToken, + number: paze_decrypted_data.token.payment_token, + expiry_month: paze_decrypted_data.token.token_expiration_month, + expiry_year: paze_decrypted_data.token.token_expiration_year, + cvc: None, + holder_name: paze_decrypted_data + .billing_address + .name + .or(item.get_optional_billing_full_name()), + brand: Some(paze_decrypted_data.payment_card_network.clone()) + .and_then(get_adyen_card_network), + network_payment_reference: None, + }; + Ok(AdyenPaymentMethod::AdyenCard(Box::new(data))) + } + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Cybersource"), + ) + .into()), + }, domain::WalletData::TwintRedirect { .. } => Ok(AdyenPaymentMethod::Twint), domain::WalletData::VippsRedirect { .. } => Ok(AdyenPaymentMethod::Vipps), domain::WalletData::DanaRedirect { .. } => Ok(AdyenPaymentMethod::Dana), @@ -2118,7 +2237,7 @@ pub fn check_required_field<'a, T>( }) } -impl<'a> +impl TryFrom<( &domain::PayLaterData, &Option, @@ -2128,7 +2247,7 @@ impl<'a> &Option>, &Option
, &Option
, - )> for AdyenPaymentMethod<'a> + )> for AdyenPaymentMethod<'_> { type Error = Error; fn try_from( @@ -2231,7 +2350,8 @@ impl<'a> check_required_field(billing_address, "billing")?; Ok(AdyenPaymentMethod::Atome) } - domain::payments::PayLaterData::KlarnaSdk { .. } => { + domain::payments::PayLaterData::KlarnaCheckout {} + | domain::payments::PayLaterData::KlarnaSdk { .. } => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Adyen"), ) @@ -2241,12 +2361,12 @@ impl<'a> } } -impl<'a> +impl TryFrom<( &domain::BankRedirectData, Option, &types::PaymentsAuthorizeRouterData, - )> for AdyenPaymentMethod<'a> + )> for AdyenPaymentMethod<'_> { type Error = Error; fn try_from( @@ -2404,11 +2524,11 @@ impl<'a> } } -impl<'a> +impl TryFrom<( &domain::BankTransferData, &types::PaymentsAuthorizeRouterData, - )> for AdyenPaymentMethod<'a> + )> for AdyenPaymentMethod<'_> { type Error = Error; fn try_from( @@ -2469,7 +2589,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for DokuBankData { } } -impl<'a> TryFrom<&domain::payments::CardRedirectData> for AdyenPaymentMethod<'a> { +impl TryFrom<&domain::payments::CardRedirectData> for AdyenPaymentMethod<'_> { type Error = Error; fn try_from( card_redirect_data: &domain::payments::CardRedirectData, @@ -2488,11 +2608,11 @@ impl<'a> TryFrom<&domain::payments::CardRedirectData> for AdyenPaymentMethod<'a> } } -impl<'a> +impl TryFrom<( &AdyenRouterData<&types::PaymentsAuthorizeRouterData>, payments::MandateReferenceId, - )> for AdyenPaymentRequest<'a> + )> for AdyenPaymentRequest<'_> { type Error = Error; fn try_from( @@ -2509,7 +2629,7 @@ impl<'a> get_recurring_processing_model(item.router_data)?; let browser_info = None; let additional_data = get_additional_data(item.router_data); - let return_url = item.router_data.request.get_return_url()?; + let return_url = item.router_data.request.get_router_return_url()?; let payment_method_type = item.router_data.request.payment_method_type; let payment_method = match mandate_ref_id { payments::MandateReferenceId::ConnectorMandateId(connector_mandate_ids) => { @@ -2519,7 +2639,9 @@ impl<'a> None => PaymentType::Scheme, }, stored_payment_method_id: Secret::new( - connector_mandate_ids.get_connector_mandate_id()?, + connector_mandate_ids + .get_connector_mandate_id() + .ok_or_else(missing_field_err("mandate_id"))?, ), }; Ok::, Self::Error>(AdyenPaymentMethod::Mandate(Box::new( @@ -2528,15 +2650,30 @@ impl<'a> } payments::MandateReferenceId::NetworkMandateId(network_mandate_id) => { match item.router_data.request.payment_method_data { - domain::PaymentMethodData::Card(ref card) => { - let card_issuer = card.get_card_issuer()?; - let brand = CardBrand::try_from(&card_issuer)?; + domain::PaymentMethodData::CardDetailsForNetworkTransactionId( + ref card_details_for_network_transaction_id, + ) => { + let brand = match card_details_for_network_transaction_id + .card_network + .clone() + .and_then(get_adyen_card_network) + { + Some(card_network) => card_network, + None => CardBrand::try_from( + &card_details_for_network_transaction_id.get_card_issuer()?, + )?, + }; + let card_holder_name = item.router_data.get_optional_billing_full_name(); let adyen_card = AdyenCard { payment_type: PaymentType::Scheme, - number: card.card_number.clone(), - expiry_month: card.card_exp_month.clone(), - expiry_year: card.card_exp_year.clone(), + number: card_details_for_network_transaction_id.card_number.clone(), + expiry_month: card_details_for_network_transaction_id + .card_exp_month + .clone(), + expiry_year: card_details_for_network_transaction_id + .get_expiry_year_4_digit() + .clone(), cvc: None, holder_name: card_holder_name, brand: Some(brand), @@ -2554,12 +2691,14 @@ impl<'a> | domain::PaymentMethodData::MandatePayment | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::Card(_) => { Err(errors::ConnectorError::NotSupported { message: "Network tokenization for payment method".to_string(), connector: "Adyen", @@ -2567,12 +2706,53 @@ impl<'a> } } } - payments::MandateReferenceId::NetworkTokenWithNTI(_) => { - Err(errors::ConnectorError::NotSupported { - message: "Network tokenization for payment method".to_string(), - connector: "Adyen", - })? - } + payments::MandateReferenceId::NetworkTokenWithNTI(network_mandate_id) => { + match item.router_data.request.payment_method_data { + domain::PaymentMethodData::NetworkToken(ref token_data) => { + let card_issuer = token_data.get_card_issuer()?; + let brand = CardBrand::try_from(&card_issuer)?; + let card_holder_name = item.router_data.get_optional_billing_full_name(); + let adyen_network_token = AdyenNetworkTokenData { + payment_type: PaymentType::NetworkToken, + number: token_data.token_number.clone(), + expiry_month: token_data.token_exp_month.clone(), + expiry_year: token_data.get_expiry_year_4_digit(), + holder_name: card_holder_name, + brand: Some(brand), // FIXME: Remove hardcoding + network_payment_reference: Some(Secret::new( + network_mandate_id.network_transaction_id, + )), + }; + Ok(AdyenPaymentMethod::NetworkToken(Box::new( + adyen_network_token, + ))) + } + + domain::PaymentMethodData::Card(_) + | 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::RealTimePayment(_) + | domain::PaymentMethodData::Upi(_) + | domain::PaymentMethodData::Voucher(_) + | domain::PaymentMethodData::GiftCard(_) + | domain::PaymentMethodData::OpenBanking(_) + | domain::PaymentMethodData::CardToken(_) + | domain::PaymentMethodData::MobilePayment(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + Err(errors::ConnectorError::NotSupported { + message: "Network tokenization for payment method".to_string(), + connector: "Adyen", + })? + } + } + } // }?; Ok(AdyenPaymentRequest { amount, @@ -2584,6 +2764,7 @@ impl<'a> recurring_processing_model, browser_info, additional_data, + mpi_data: None, telephone_number: None, shopper_name: None, shopper_email: None, @@ -2603,11 +2784,11 @@ impl<'a> }) } } -impl<'a> +impl TryFrom<( &AdyenRouterData<&types::PaymentsAuthorizeRouterData>, &domain::Card, - )> for AdyenPaymentRequest<'a> + )> for AdyenPaymentRequest<'_> { type Error = Error; fn try_from( @@ -2631,7 +2812,7 @@ impl<'a> get_address_info(item.router_data.get_optional_billing()).and_then(Result::ok); let country_code = get_country_code(item.router_data.get_optional_billing()); let additional_data = get_additional_data(item.router_data); - let return_url = item.router_data.request.get_return_url()?; + let return_url = item.router_data.request.get_router_return_url()?; let card_holder_name = item.router_data.get_optional_billing_full_name(); let payment_method = AdyenPaymentMethod::try_from((card_data, card_holder_name))?; let shopper_email = item.router_data.get_optional_billing_email(); @@ -2647,6 +2828,7 @@ impl<'a> recurring_processing_model, browser_info, additional_data, + mpi_data: None, telephone_number: None, shopper_name, shopper_email, @@ -2667,11 +2849,11 @@ impl<'a> } } -impl<'a> +impl TryFrom<( &AdyenRouterData<&types::PaymentsAuthorizeRouterData>, &domain::BankDebitData, - )> for AdyenPaymentRequest<'a> + )> for AdyenPaymentRequest<'_> { type Error = Error; @@ -2689,7 +2871,7 @@ impl<'a> get_recurring_processing_model(item.router_data)?; 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 return_url = item.router_data.request.get_router_return_url()?; let payment_method = AdyenPaymentMethod::try_from((bank_debit_data, item.router_data))?; let country_code = get_country_code(item.router_data.get_optional_billing()); let request = AdyenPaymentRequest { @@ -2702,6 +2884,7 @@ impl<'a> shopper_interaction, recurring_processing_model, additional_data, + mpi_data: None, shopper_name: None, shopper_locale: None, shopper_email: item.router_data.get_optional_billing_email(), @@ -2723,11 +2906,11 @@ impl<'a> } } -impl<'a> +impl TryFrom<( &AdyenRouterData<&types::PaymentsAuthorizeRouterData>, &domain::VoucherData, - )> for AdyenPaymentRequest<'a> + )> for AdyenPaymentRequest<'_> { type Error = Error; @@ -2745,7 +2928,7 @@ impl<'a> let browser_info = get_browser_info(item.router_data)?; let additional_data = get_additional_data(item.router_data); let payment_method = AdyenPaymentMethod::try_from((voucher_data, item.router_data))?; - let return_url = item.router_data.request.get_return_url()?; + let return_url = item.router_data.request.get_router_return_url()?; let social_security_number = get_social_security_number(voucher_data); let billing_address = get_address_info(item.router_data.get_optional_billing()).and_then(Result::ok); @@ -2765,6 +2948,7 @@ impl<'a> shopper_locale: None, shopper_email: item.router_data.get_optional_billing_email(), social_security_number, + mpi_data: None, telephone_number: None, billing_address, delivery_address: None, @@ -2782,11 +2966,11 @@ impl<'a> } } -impl<'a> +impl TryFrom<( &AdyenRouterData<&types::PaymentsAuthorizeRouterData>, &domain::BankTransferData, - )> for AdyenPaymentRequest<'a> + )> for AdyenPaymentRequest<'_> { type Error = Error; @@ -2801,7 +2985,7 @@ impl<'a> let auth_type = AdyenAuthType::try_from(&item.router_data.connector_auth_type)?; let shopper_interaction = AdyenShopperInteraction::from(item.router_data); let payment_method = AdyenPaymentMethod::try_from((bank_transfer_data, item.router_data))?; - let return_url = item.router_data.request.get_return_url()?; + let return_url = item.router_data.request.get_router_return_url()?; let request = AdyenPaymentRequest { amount, merchant_account: auth_type.merchant_account, @@ -2812,6 +2996,7 @@ impl<'a> shopper_interaction, recurring_processing_model: None, additional_data: None, + mpi_data: None, shopper_name: None, shopper_locale: None, shopper_email: item.router_data.get_optional_billing_email(), @@ -2833,11 +3018,11 @@ impl<'a> } } -impl<'a> +impl TryFrom<( &AdyenRouterData<&types::PaymentsAuthorizeRouterData>, &domain::GiftCardData, - )> for AdyenPaymentRequest<'a> + )> for AdyenPaymentRequest<'_> { type Error = Error; @@ -2863,6 +3048,7 @@ impl<'a> shopper_interaction, recurring_processing_model: None, additional_data: None, + mpi_data: None, shopper_name: None, shopper_locale: None, shopper_email: item.router_data.get_optional_billing_email(), @@ -2884,11 +3070,11 @@ impl<'a> } } -impl<'a> +impl TryFrom<( &AdyenRouterData<&types::PaymentsAuthorizeRouterData>, &domain::BankRedirectData, - )> for AdyenPaymentRequest<'a> + )> for AdyenPaymentRequest<'_> { type Error = Error; fn try_from( @@ -2905,7 +3091,7 @@ impl<'a> get_recurring_processing_model(item.router_data)?; 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 return_url = item.router_data.request.get_router_return_url()?; let payment_method = AdyenPaymentMethod::try_from(( bank_redirect_data, item.router_data.test_mode, @@ -2926,6 +3112,7 @@ impl<'a> recurring_processing_model, browser_info, additional_data, + mpi_data: None, telephone_number: None, shopper_name: None, shopper_email: item.router_data.get_optional_billing_email(), @@ -2988,11 +3175,11 @@ fn get_shopper_email( } } -impl<'a> +impl TryFrom<( &AdyenRouterData<&types::PaymentsAuthorizeRouterData>, &domain::WalletData, - )> for AdyenPaymentRequest<'a> + )> for AdyenPaymentRequest<'_> { type Error = Error; fn try_from( @@ -3008,13 +3195,30 @@ impl<'a> let additional_data = get_additional_data(item.router_data); let payment_method = AdyenPaymentMethod::try_from((wallet_data, item.router_data))?; let shopper_interaction = AdyenShopperInteraction::from(item.router_data); - let channel = get_channel_type(&item.router_data.request.payment_method_type); + let channel = get_channel_type(item.router_data.request.payment_method_type); let (recurring_processing_model, store_payment_method, shopper_reference) = get_recurring_processing_model(item.router_data)?; let return_url = item.router_data.request.get_router_return_url()?; let shopper_email = get_shopper_email(item.router_data, store_payment_method.is_some())?; let billing_address = get_address_info(item.router_data.get_optional_billing()).and_then(Result::ok); + let mpi_data = if let domain::WalletData::Paze(_) = wallet_data { + match item.router_data.payment_method_token.clone() { + Some(types::PaymentMethodToken::PazeDecrypt(paze_decrypted_data)) => { + Some(AdyenMpiData { + directory_response: "Y".to_string(), + authentication_response: "Y".to_string(), + token_authentication_verification_value: paze_decrypted_data + .token + .payment_account_reference, + eci: paze_decrypted_data.eci, + }) + } + _ => None, + } + } else { + None + }; Ok(AdyenPaymentRequest { amount, merchant_account: auth_type.merchant_account, @@ -3025,6 +3229,7 @@ impl<'a> recurring_processing_model, browser_info, additional_data, + mpi_data, telephone_number: None, shopper_name: None, shopper_email, @@ -3045,11 +3250,11 @@ impl<'a> } } -impl<'a> +impl TryFrom<( &AdyenRouterData<&types::PaymentsAuthorizeRouterData>, &domain::PayLaterData, - )> for AdyenPaymentRequest<'a> + )> for AdyenPaymentRequest<'_> { type Error = Error; fn try_from( @@ -3071,7 +3276,7 @@ impl<'a> ); let (recurring_processing_model, store_payment_method, _) = get_recurring_processing_model(item.router_data)?; - let return_url = item.router_data.request.get_return_url()?; + let return_url = item.router_data.request.get_router_return_url()?; let shopper_name = get_shopper_name(item.router_data.get_optional_billing()); let shopper_email = item.router_data.get_optional_billing_email(); let billing_address = @@ -3103,6 +3308,7 @@ impl<'a> telephone_number, shopper_name, shopper_email, + mpi_data: None, shopper_locale: None, social_security_number: None, billing_address, @@ -3120,11 +3326,11 @@ impl<'a> } } -impl<'a> +impl TryFrom<( &AdyenRouterData<&types::PaymentsAuthorizeRouterData>, &domain::payments::CardRedirectData, - )> for AdyenPaymentRequest<'a> + )> for AdyenPaymentRequest<'_> { type Error = Error; fn try_from( @@ -3138,7 +3344,7 @@ impl<'a> let auth_type = AdyenAuthType::try_from(&item.router_data.connector_auth_type)?; let payment_method = AdyenPaymentMethod::try_from(card_redirect_data)?; let shopper_interaction = AdyenShopperInteraction::from(item.router_data); - let return_url = item.router_data.request.get_return_url()?; + let return_url = item.router_data.request.get_router_return_url()?; let shopper_name = get_shopper_name(item.router_data.get_optional_billing()); let shopper_email = item.router_data.get_optional_billing_email(); let telephone_number = item @@ -3159,6 +3365,7 @@ impl<'a> recurring_processing_model: None, browser_info: None, additional_data: None, + mpi_data: None, telephone_number, shopper_name, shopper_email, @@ -3203,8 +3410,8 @@ impl TryFrom> resource_id: types::ResponseId::ConnectorTransactionId( item.response.payment_psp_reference, ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(item.response.reference), @@ -3238,8 +3445,8 @@ impl Ok(Self { response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(item.response.psp_reference), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -3270,7 +3477,10 @@ pub fn get_adyen_response( > { let status = storage_enums::AttemptStatus::foreign_from((is_capture_manual, response.result_code, pmt)); - let error = if response.refusal_reason.is_some() || response.refusal_reason_code.is_some() { + let error = if response.refusal_reason.is_some() + || response.refusal_reason_code.is_some() + || status == storage_enums::AttemptStatus::Failure + { Some(types::ErrorResponse { code: response .refusal_reason_code @@ -3295,6 +3505,7 @@ pub fn get_adyen_response( connector_mandate_id: Some(mandate_id.expose()), payment_method_id: None, mandate_metadata: None, + connector_mandate_request_reference_id: None, }); let network_txn_id = response.additional_data.and_then(|additional_data| { additional_data @@ -3304,8 +3515,8 @@ pub fn get_adyen_response( let payments_response_data = types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(response.psp_reference), - redirection_data: None, - mandate_reference, + redirection_data: Box::new(None), + mandate_reference: Box::new(mandate_reference), connector_metadata: None, network_txn_id, connector_response_reference_id: Some(response.merchant_reference), @@ -3332,7 +3543,10 @@ pub fn get_webhook_response( is_capture_manual, response.status.clone(), ))?; - let error = if response.refusal_reason.is_some() || response.refusal_reason_code.is_some() { + let error = if response.refusal_reason.is_some() + || response.refusal_reason_code.is_some() + || status == storage_enums::AttemptStatus::Failure + { Some(types::ErrorResponse { code: response .refusal_reason_code @@ -3368,8 +3582,8 @@ pub fn get_webhook_response( .payment_reference .unwrap_or(response.transaction_id), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(response.merchant_reference_id), @@ -3398,7 +3612,10 @@ pub fn get_redirection_response( response.result_code.clone(), pmt, )); - let error = if response.refusal_reason.is_some() || response.refusal_reason_code.is_some() { + let error = if response.refusal_reason.is_some() + || response.refusal_reason_code.is_some() + || status == storage_enums::AttemptStatus::Failure + { Some(types::ErrorResponse { code: response .refusal_reason_code @@ -3438,8 +3655,8 @@ pub fn get_redirection_response( Some(psp) => types::ResponseId::ConnectorTransactionId(psp.to_string()), None => types::ResponseId::NoResponseId, }, - redirection_data, - mandate_reference: None, + redirection_data: Box::new(redirection_data), + mandate_reference: Box::new(None), connector_metadata, network_txn_id: None, connector_response_reference_id: response @@ -3470,7 +3687,10 @@ pub fn get_present_to_shopper_response( response.result_code.clone(), pmt, )); - let error = if response.refusal_reason.is_some() || response.refusal_reason_code.is_some() { + let error = if response.refusal_reason.is_some() + || response.refusal_reason_code.is_some() + || status == storage_enums::AttemptStatus::Failure + { Some(types::ErrorResponse { code: response .refusal_reason_code @@ -3496,8 +3716,8 @@ pub fn get_present_to_shopper_response( Some(psp) => types::ResponseId::ConnectorTransactionId(psp.to_string()), None => types::ResponseId::NoResponseId, }, - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata, network_txn_id: None, connector_response_reference_id: response @@ -3528,7 +3748,10 @@ pub fn get_qr_code_response( response.result_code.clone(), pmt, )); - let error = if response.refusal_reason.is_some() || response.refusal_reason_code.is_some() { + let error = if response.refusal_reason.is_some() + || response.refusal_reason_code.is_some() + || status == storage_enums::AttemptStatus::Failure + { Some(types::ErrorResponse { code: response .refusal_reason_code @@ -3553,8 +3776,8 @@ pub fn get_qr_code_response( Some(psp) => types::ResponseId::ConnectorTransactionId(psp.to_string()), None => types::ResponseId::NoResponseId, }, - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata, network_txn_id: None, connector_response_reference_id: response @@ -3596,8 +3819,8 @@ pub fn get_redirection_error_response( // We don't get connector transaction id for redirections in Adyen. let payments_response_data = types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::NoResponseId, - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: response @@ -3966,8 +4189,8 @@ impl TryFrom> status: storage_enums::AttemptStatus::Pending, response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(connector_transaction_id), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(item.response.reference), @@ -4098,6 +4321,10 @@ pub struct AdyenAdditionalDataWH { pub chargeback_reason_code: Option, #[serde(default, with = "common_utils::custom_serde::iso8601::option")] pub defense_period_ends_at: Option, + /// Enable recurring details in dashboard to receive this ID, https://docs.adyen.com/online-payments/tokenization/create-and-use-tokens#test-and-go-live + #[serde(rename = "recurring.recurringDetailReference")] + pub recurring_detail_reference: Option>, + pub network_tx_reference: Option>, } #[derive(Debug, Deserialize)] @@ -4296,6 +4523,14 @@ pub struct AdyenIncomingWebhook { impl From for AdyenWebhookResponse { fn from(notif: AdyenNotificationRequestItemWH) -> Self { + let (refusal_reason, refusal_reason_code) = if !is_success_scenario(notif.success.clone()) { + ( + notif.reason.or(Some(consts::NO_ERROR_MESSAGE.to_string())), + Some(consts::NO_ERROR_CODE.to_string()), + ) + } else { + (None, None) + }; Self { transaction_id: notif.psp_reference, payment_reference: notif.original_reference, @@ -4354,8 +4589,8 @@ impl From for AdyenWebhookResponse { currency: notif.amount.currency, }), merchant_reference_id: notif.merchant_reference, - refusal_reason: None, - refusal_reason_code: None, + refusal_reason, + refusal_reason_code, event_code: notif.event_code, } } @@ -4689,7 +4924,8 @@ impl TryFrom<&AdyenRouterData<&types::PayoutsRouterData>> for AdyenPayoutC })?, }; let bank_data = PayoutBankData { bank: bank_details }; - let address: &payments::AddressDetails = item.router_data.get_billing_address()?; + let address: &hyperswitch_domain_models::address::AddressDetails = + item.router_data.get_billing_address()?; Ok(Self { amount: Amount { value: item.amount.to_owned(), @@ -4731,7 +4967,8 @@ impl TryFrom<&AdyenRouterData<&types::PayoutsRouterData>> for AdyenPayoutC })? } }; - let address: &payments::AddressDetails = item.router_data.get_billing_address()?; + let address: &hyperswitch_domain_models::address::AddressDetails = + item.router_data.get_billing_address()?; let payout_wallet = PayoutWalletData { selected_brand: PayoutBrand::Paypal, additional_data, @@ -4925,7 +5162,6 @@ impl TryFrom<&types::DefendDisputeRouterData> for AdyenDefendDisputeRequest { #[derive(Default, Debug, Serialize)] #[serde(rename_all = "camelCase")] - pub struct Evidence { defense_documents: Vec, merchant_account_code: Secret, @@ -4934,7 +5170,6 @@ pub struct Evidence { #[derive(Default, Debug, Serialize)] #[serde(rename_all = "camelCase")] - pub struct DefenseDocuments { content: Secret, content_type: Option, @@ -5160,3 +5395,97 @@ impl ForeignTryFrom<(&Self, AdyenDisputeResponse)> for types::DefendDisputeRoute } } } + +impl TryFrom<(&domain::NetworkTokenData, Option>)> for AdyenPaymentMethod<'_> { + type Error = Error; + fn try_from( + (token_data, card_holder_name): (&domain::NetworkTokenData, Option>), + ) -> Result { + let adyen_network_token = AdyenNetworkTokenData { + payment_type: PaymentType::NetworkToken, + number: token_data.token_number.clone(), + expiry_month: token_data.token_exp_month.clone(), + expiry_year: token_data.get_expiry_year_4_digit(), + holder_name: card_holder_name, + brand: None, // FIXME: Remove hardcoding + network_payment_reference: None, + }; + Ok(AdyenPaymentMethod::NetworkToken(Box::new( + adyen_network_token, + ))) + } +} + +impl + TryFrom<( + &AdyenRouterData<&types::PaymentsAuthorizeRouterData>, + &domain::NetworkTokenData, + )> for AdyenPaymentRequest<'_> +{ + type Error = Error; + fn try_from( + value: ( + &AdyenRouterData<&types::PaymentsAuthorizeRouterData>, + &domain::NetworkTokenData, + ), + ) -> Result { + let (item, token_data) = value; + let amount = get_amount_data(item); + let auth_type = AdyenAuthType::try_from(&item.router_data.connector_auth_type)?; + let shopper_interaction = AdyenShopperInteraction::from(item.router_data); + let shopper_reference = build_shopper_reference( + &item.router_data.customer_id, + item.router_data.merchant_id.clone(), + ); + let (recurring_processing_model, store_payment_method, _) = + get_recurring_processing_model(item.router_data)?; + let browser_info = get_browser_info(item.router_data)?; + let billing_address = + get_address_info(item.router_data.get_optional_billing()).transpose()?; + let country_code = get_country_code(item.router_data.get_optional_billing()); + let additional_data = get_additional_data(item.router_data); + let return_url = item.router_data.request.get_router_return_url()?; + let card_holder_name = item.router_data.get_optional_billing_full_name(); + let payment_method = AdyenPaymentMethod::try_from((token_data, card_holder_name))?; + let shopper_email = item.router_data.request.email.clone(); + let shopper_name = get_shopper_name(item.router_data.get_optional_billing()); + let mpi_data = AdyenMpiData { + directory_response: "Y".to_string(), + authentication_response: "Y".to_string(), + token_authentication_verification_value: token_data + .token_cryptogram + .clone() + .unwrap_or_default(), + eci: Some("02".to_string()), + }; + + Ok(AdyenPaymentRequest { + amount, + merchant_account: auth_type.merchant_account, + payment_method, + reference: item.router_data.connector_request_reference_id.clone(), + return_url, + shopper_interaction, + recurring_processing_model, + browser_info, + additional_data, + telephone_number: None, + shopper_name, + shopper_email, + shopper_locale: None, + social_security_number: None, + billing_address, + delivery_address: None, + country_code, + line_items: None, + shopper_reference, + store_payment_method, + channel: None, + shopper_statement: item.router_data.request.statement_descriptor.clone(), + shopper_ip: item.router_data.request.get_ip_address_as_optional(), + metadata: item.router_data.request.metadata.clone().map(Into::into), + merchant_order_reference: item.router_data.request.merchant_order_reference_id.clone(), + mpi_data: Some(mpi_data), + }) + } +} diff --git a/crates/router/src/connector/adyenplatform.rs b/crates/router/src/connector/adyenplatform.rs index 3da1a2d33be2..f95e9cd4d649 100644 --- a/crates/router/src/connector/adyenplatform.rs +++ b/crates/router/src/connector/adyenplatform.rs @@ -13,6 +13,8 @@ use error_stack::report; use error_stack::ResultExt; #[cfg(feature = "payouts")] use http::HeaderName; +use hyperswitch_interfaces::webhooks::IncomingWebhookFlowError; +use masking::Maskable; #[cfg(feature = "payouts")] use masking::Secret; #[cfg(feature = "payouts")] @@ -27,11 +29,7 @@ use crate::{ configs::settings, core::errors::{self, CustomResult}, headers, - services::{ - self, - request::{self, Mask}, - ConnectorValidation, - }, + services::{self, request::Mask, ConnectorSpecifications, ConnectorValidation}, types::{ self, api::{self, ConnectorCommon}, @@ -67,7 +65,7 @@ impl ConnectorCommon for Adyenplatform { fn get_auth_header( &self, auth_type: &types::ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { + ) -> CustomResult)>, errors::ConnectorError> { let auth = adyenplatform::AdyenplatformAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; Ok(vec![( @@ -209,7 +207,7 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + ) -> CustomResult)>, errors::ConnectorError> { let mut header = vec![( headers::CONTENT_TYPE.to_string(), types::PayoutFulfillType::get_content_type(self) @@ -401,6 +399,25 @@ impl api::IncomingWebhook for Adyenplatform { } } + fn get_webhook_api_response( + &self, + _request: &api::IncomingWebhookRequestDetails<'_>, + error_kind: Option, + ) -> CustomResult, errors::ConnectorError> + { + if error_kind.is_some() { + Ok(services::api::ApplicationResponse::JsonWithHeaders(( + serde_json::Value::Null, + vec![( + "x-http-code".to_string(), + Maskable::Masked(Secret::new("404".to_string())), + )], + ))) + } else { + Ok(services::api::ApplicationResponse::StatusOk) + } + } + fn get_webhook_event_type( &self, #[cfg(feature = "payouts")] request: &api::IncomingWebhookRequestDetails<'_>, @@ -444,3 +461,5 @@ impl api::IncomingWebhook for Adyenplatform { } } } + +impl ConnectorSpecifications for Adyenplatform {} diff --git a/crates/router/src/connector/adyenplatform/transformers/payouts.rs b/crates/router/src/connector/adyenplatform/transformers/payouts.rs index e2a72ad52b8d..f3f398760c45 100644 --- a/crates/router/src/connector/adyenplatform/transformers/payouts.rs +++ b/crates/router/src/connector/adyenplatform/transformers/payouts.rs @@ -359,7 +359,6 @@ pub struct AdyenplatformIncomingWebhook { pub struct AdyenplatformIncomingWebhookData { pub status: AdyenplatformWebhookStatus, pub reference: String, - pub priority: AdyenPayoutPriority, pub tracking: Option, } diff --git a/crates/router/src/connector/authorizedotnet.rs b/crates/router/src/connector/authorizedotnet.rs index b48643e76e88..afc8f063e4eb 100644 --- a/crates/router/src/connector/authorizedotnet.rs +++ b/crates/router/src/connector/authorizedotnet.rs @@ -19,7 +19,7 @@ use crate::{ }, events::connector_api_logs::ConnectorEvent, headers, - services::{self, request, ConnectorIntegration, ConnectorValidation}, + services::{self, request, ConnectorIntegration, ConnectorSpecifications, ConnectorValidation}, types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt, PaymentsCompleteAuthorize}, @@ -66,14 +66,17 @@ impl ConnectorCommon for Authorizedotnet { } impl ConnectorValidation for Authorizedotnet { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( connector_utils::construct_not_supported_error_report(capture_method, self.id()), ), @@ -1055,3 +1058,5 @@ impl services::ConnectorRedirectResponse for Authorizedotnet { } } } + +impl ConnectorSpecifications for Authorizedotnet {} diff --git a/crates/router/src/connector/authorizedotnet/transformers.rs b/crates/router/src/connector/authorizedotnet/transformers.rs index 868409dafc79..9698b4ac5d83 100644 --- a/crates/router/src/connector/authorizedotnet/transformers.rs +++ b/crates/router/src/connector/authorizedotnet/transformers.rs @@ -339,12 +339,14 @@ impl TryFrom<&types::SetupMandateRouterData> for CreateCustomerProfileRequest { | domain::PaymentMethodData::MandatePayment | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("authorizedotnet"), ))? @@ -387,20 +389,20 @@ impl 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( + redirection_data: Box::new(None), + mandate_reference: Box::new(item.response.customer_profile_id.map( |customer_profile_id| types::MandateReference { - connector_mandate_id: item - .response - .customer_payment_profile_id_list - .first() - .map(|payment_profile_id| { - format!("{customer_profile_id}-{payment_profile_id}") - }), + connector_mandate_id: + item.response.customer_payment_profile_id_list.first().map( + |payment_profile_id| { + format!("{customer_profile_id}-{payment_profile_id}") + }, + ), payment_method_id: None, mandate_metadata: None, + connector_mandate_request_reference_id: None, }, - ), + )), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -466,7 +468,9 @@ impl TryFrom for AuthorizationType { fn try_from(capture_method: enums::CaptureMethod) -> Result { match capture_method { enums::CaptureMethod::Manual => Ok(Self::Pre), - enums::CaptureMethod::Automatic => Ok(Self::Final), + enums::CaptureMethod::SequentialAutomatic | enums::CaptureMethod::Automatic => { + Ok(Self::Final) + } enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( utils::construct_not_supported_error_report(capture_method, "authorizedotnet"), )?, @@ -525,12 +529,14 @@ impl TryFrom<&AuthorizedotnetRouterData<&types::PaymentsAuthorizeRouterData>> | domain::PaymentMethodData::MandatePayment | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message( "authorizedotnet", @@ -585,12 +591,14 @@ impl | domain::PaymentMethodData::MandatePayment | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("authorizedotnet"), ))? @@ -645,7 +653,7 @@ impl ), ) -> Result { let mandate_id = connector_mandate_id - .connector_mandate_id + .get_connector_mandate_id() .ok_or(errors::ConnectorError::MissingConnectorMandateID)?; Ok(Self { transaction_type: TransactionType::try_from(item.router_data.request.capture_method)?, @@ -1111,6 +1119,7 @@ impl ), payment_method_id: None, mandate_metadata: None, + connector_mandate_request_reference_id: None, } }); @@ -1122,8 +1131,8 @@ impl resource_id: types::ResponseId::ConnectorTransactionId( transaction_response.transaction_id.clone(), ), - redirection_data, - mandate_reference, + redirection_data: Box::new(redirection_data), + mandate_reference: Box::new(mandate_reference), connector_metadata: metadata, network_txn_id: transaction_response .network_trans_id @@ -1195,8 +1204,8 @@ impl resource_id: types::ResponseId::ConnectorTransactionId( transaction_response.transaction_id.clone(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: metadata, network_txn_id: transaction_response .network_trans_id @@ -1525,8 +1534,8 @@ impl resource_id: types::ResponseId::ConnectorTransactionId( transaction.transaction_id.clone(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(transaction.transaction_id.clone()), @@ -1572,7 +1581,9 @@ impl TryFrom> for TransactionType { fn try_from(capture_method: Option) -> Result { match capture_method { Some(enums::CaptureMethod::Manual) => Ok(Self::Authorization), - Some(enums::CaptureMethod::Automatic) | None => Ok(Self::Payment), + Some(enums::CaptureMethod::SequentialAutomatic) + | Some(enums::CaptureMethod::Automatic) + | None => Ok(Self::Payment), Some(enums::CaptureMethod::ManualMultiple) => { Err(utils::construct_not_supported_error_report( enums::CaptureMethod::ManualMultiple, @@ -1747,6 +1758,7 @@ fn get_wallet_data( | domain::WalletData::MbWayRedirect(_) | domain::WalletData::MobilePayRedirect(_) | domain::WalletData::PaypalSdk(_) + | domain::WalletData::Paze(_) | domain::WalletData::SamsungPay(_) | domain::WalletData::TwintRedirect {} | domain::WalletData::VippsRedirect {} @@ -1826,7 +1838,9 @@ impl TryFrom<&AuthorizedotnetRouterData<&types::PaymentsCompleteAuthorizeRouterD .payer_id; let transaction_type = match item.router_data.request.capture_method { Some(enums::CaptureMethod::Manual) => Ok(TransactionType::ContinueAuthorization), - Some(enums::CaptureMethod::Automatic) | None => Ok(TransactionType::ContinueCapture), + Some(enums::CaptureMethod::SequentialAutomatic) + | Some(enums::CaptureMethod::Automatic) + | None => Ok(TransactionType::ContinueCapture), Some(enums::CaptureMethod::ManualMultiple) => { Err(errors::ConnectorError::NotSupported { message: enums::CaptureMethod::ManualMultiple.to_string(), diff --git a/crates/router/src/connector/bankofamerica.rs b/crates/router/src/connector/bankofamerica.rs index daec033f6f36..95ceee555895 100644 --- a/crates/router/src/connector/bankofamerica.rs +++ b/crates/router/src/connector/bankofamerica.rs @@ -25,7 +25,7 @@ use crate::{ services::{ self, request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, + ConnectorIntegration, ConnectorSpecifications, ConnectorValidation, }, types::{ self, @@ -278,14 +278,17 @@ impl ConnectorCommon for Bankofamerica { } impl ConnectorValidation for Bankofamerica { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( connector_utils::construct_not_implemented_error_report(capture_method, self.id()), ), @@ -1063,3 +1066,5 @@ impl api::IncomingWebhook for Bankofamerica { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for Bankofamerica {} diff --git a/crates/router/src/connector/bankofamerica/transformers.rs b/crates/router/src/connector/bankofamerica/transformers.rs index f47ab4139e8b..71cc006700e4 100644 --- a/crates/router/src/connector/bankofamerica/transformers.rs +++ b/crates/router/src/connector/bankofamerica/transformers.rs @@ -1,4 +1,3 @@ -use api_models::payments; use base64::Engine; use common_utils::pii; use masking::{ExposeInterface, PeekInterface, Secret}; @@ -297,6 +296,7 @@ impl TryFrom<&types::SetupMandateRouterData> for BankOfAmericaPaymentsRequest { | domain::WalletData::MobilePayRedirect(_) | domain::WalletData::PaypalRedirect(_) | domain::WalletData::PaypalSdk(_) + | domain::WalletData::Paze(_) | domain::WalletData::SamsungPay(_) | domain::WalletData::TwintRedirect {} | domain::WalletData::VippsRedirect {} @@ -318,12 +318,14 @@ impl TryFrom<&types::SetupMandateRouterData> for BankOfAmericaPaymentsRequest { | domain::PaymentMethodData::MandatePayment | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("BankOfAmerica"), ))? @@ -360,6 +362,7 @@ impl .map(|payment_instrument| payment_instrument.id.expose()), payment_method_id: None, mandate_metadata: None, + connector_mandate_request_reference_id: None, } }); let mut mandate_status = @@ -396,6 +399,7 @@ impl | common_enums::PaymentMethod::BankDebit | common_enums::PaymentMethod::Reward | common_enums::PaymentMethod::RealTimePayment + | common_enums::PaymentMethod::MobilePayment | common_enums::PaymentMethod::Upi | common_enums::PaymentMethod::Voucher | common_enums::PaymentMethod::OpenBanking @@ -410,8 +414,8 @@ impl resource_id: types::ResponseId::ConnectorTransactionId( info_response.id.clone(), ), - redirection_data: None, - mandate_reference, + redirection_data: Box::new(None), + mandate_reference: Box::new(mandate_reference), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some( @@ -489,7 +493,7 @@ impl // } fn build_bill_to( - address_details: Option<&payments::Address>, + address_details: Option<&hyperswitch_domain_models::address::Address>, email: pii::Email, ) -> Result> { let default_address = BillTo { @@ -535,6 +539,22 @@ impl From for String { } } +fn get_boa_card_type(card_network: common_enums::CardNetwork) -> Option<&'static str> { + match card_network { + common_enums::CardNetwork::Visa => Some("001"), + common_enums::CardNetwork::Mastercard => Some("002"), + common_enums::CardNetwork::AmericanExpress => Some("003"), + common_enums::CardNetwork::JCB => Some("007"), + common_enums::CardNetwork::DinersClub => Some("005"), + common_enums::CardNetwork::Discover => Some("004"), + common_enums::CardNetwork::CartesBancaires => Some("006"), + common_enums::CardNetwork::UnionPay => Some("062"), + //"042" is the type code for Masetro Cards(International). For Maestro Cards(UK-Domestic) the mapping should be "024" + common_enums::CardNetwork::Maestro => Some("042"), + common_enums::CardNetwork::Interac | common_enums::CardNetwork::RuPay => None, + } +} + #[derive(Debug, Serialize)] pub enum PaymentSolution { ApplePay, @@ -961,6 +981,9 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> "Bank Of America" ))? } + types::PaymentMethodToken::PazeDecrypt(_) => Err( + unimplemented_payment_method!("Paze", "Bank Of America"), + )?, }, None => { let email = item.router_data.request.get_email()?; @@ -1035,6 +1058,7 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> | domain::WalletData::MobilePayRedirect(_) | domain::WalletData::PaypalRedirect(_) | domain::WalletData::PaypalSdk(_) + | domain::WalletData::Paze(_) | domain::WalletData::SamsungPay(_) | domain::WalletData::TwintRedirect {} | domain::WalletData::VippsRedirect {} @@ -1071,12 +1095,14 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> | domain::PaymentMethodData::Crypto(_) | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message( "Bank of America", @@ -1489,12 +1515,13 @@ fn get_payment_response( .map(|payment_instrument| payment_instrument.id.expose()), payment_method_id: None, mandate_metadata: None, + connector_mandate_request_reference_id: None, }); Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(info_response.id.clone()), - redirection_data: None, - mandate_reference, + redirection_data: Box::new(None), + mandate_reference: Box::new(mandate_reference), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some( @@ -1562,6 +1589,7 @@ impl | common_enums::PaymentMethod::BankDebit | common_enums::PaymentMethod::Reward | common_enums::PaymentMethod::RealTimePayment + | common_enums::PaymentMethod::MobilePayment | common_enums::PaymentMethod::Upi | common_enums::PaymentMethod::Voucher | common_enums::PaymentMethod::OpenBanking @@ -1779,6 +1807,7 @@ impl | common_enums::PaymentMethod::BankDebit | common_enums::PaymentMethod::Reward | common_enums::PaymentMethod::RealTimePayment + | common_enums::PaymentMethod::MobilePayment | common_enums::PaymentMethod::Upi | common_enums::PaymentMethod::Voucher | common_enums::PaymentMethod::OpenBanking @@ -1806,8 +1835,8 @@ impl resource_id: types::ResponseId::ConnectorTransactionId( item.response.id.clone(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: item @@ -1829,8 +1858,8 @@ impl resource_id: types::ResponseId::ConnectorTransactionId( item.response.id.clone(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(item.response.id), @@ -2312,6 +2341,9 @@ impl TryFrom<(&types::SetupMandateRouterData, domain::ApplePayWalletData)> "Manual", "Bank Of America" ))?, + types::PaymentMethodToken::PazeDecrypt(_) => { + Err(unimplemented_payment_method!("Paze", "Bank Of America"))? + } }, None => PaymentInformation::from(&apple_pay_data), }; @@ -2418,10 +2450,9 @@ impl TryFrom<&domain::Card> for PaymentInformation { type Error = error_stack::Report; fn try_from(ccard: &domain::Card) -> Result { - let card_issuer = ccard.get_card_issuer(); - let card_type = match card_issuer { - Ok(issuer) => Some(String::from(issuer)), - Err(_) => None, + let card_type = match ccard.card_network.clone().and_then(get_boa_card_type) { + Some(card_network) => Some(card_network.to_string()), + None => ccard.get_card_issuer().ok().map(String::from), }; Ok(Self::Cards(Box::new(CardPaymentInformation { card: Card { diff --git a/crates/router/src/connector/braintree.rs b/crates/router/src/connector/braintree.rs index 85b310397b26..7a88180c64d2 100644 --- a/crates/router/src/connector/braintree.rs +++ b/crates/router/src/connector/braintree.rs @@ -1,5 +1,4 @@ pub mod transformers; -use std::str::FromStr; use api_models::webhooks::IncomingWebhookEvent; use base64::Engine; @@ -11,6 +10,7 @@ use common_utils::{ }; use diesel_models::enums; use error_stack::{report, Report, ResultExt}; +use hyperswitch_interfaces::webhooks::IncomingWebhookFlowError; use masking::{ExposeInterface, PeekInterface, Secret}; use ring::hmac; use sha1::{Digest, Sha1}; @@ -30,7 +30,7 @@ use crate::{ services::{ self, request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, + ConnectorIntegration, ConnectorSpecifications, ConnectorValidation, }, types::{ self, @@ -173,14 +173,17 @@ impl ConnectorCommon for Braintree { } impl ConnectorValidation for Braintree { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( connector_utils::construct_not_implemented_error_report(capture_method, self.id()), ), @@ -981,6 +984,7 @@ impl api::IncomingWebhook for Braintree { fn get_webhook_api_response( &self, _request: &api::IncomingWebhookRequestDetails<'_>, + _error_kind: Option, ) -> CustomResult, errors::ConnectorError> { Ok(services::api::ApplicationResponse::TextPlain( @@ -998,25 +1002,21 @@ impl api::IncomingWebhook for Braintree { let response = decode_webhook_payload(notif.bt_payload.replace('\n', "").as_bytes())?; match response.dispute { - Some(dispute_data) => { - let currency = enums::Currency::from_str(dispute_data.currency_iso_code.as_str()) - .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; - Ok(api::disputes::DisputePayload { - amount: connector_utils::to_currency_lower_unit( - dispute_data.amount_disputed.to_string(), - currency, - )?, - currency: dispute_data.currency_iso_code, - dispute_stage: transformers::get_dispute_stage(dispute_data.kind.as_str())?, - connector_dispute_id: dispute_data.id, - connector_reason: dispute_data.reason, - connector_reason_code: dispute_data.reason_code, - challenge_required_by: dispute_data.reply_by_date, - connector_status: dispute_data.status, - created_at: dispute_data.created_at, - updated_at: dispute_data.updated_at, - }) - } + Some(dispute_data) => Ok(api::disputes::DisputePayload { + amount: connector_utils::to_currency_lower_unit( + dispute_data.amount_disputed.to_string(), + dispute_data.currency_iso_code, + )?, + currency: dispute_data.currency_iso_code, + dispute_stage: transformers::get_dispute_stage(dispute_data.kind.as_str())?, + connector_dispute_id: dispute_data.id, + connector_reason: dispute_data.reason, + connector_reason_code: dispute_data.reason_code, + challenge_required_by: dispute_data.reply_by_date, + connector_status: dispute_data.status, + created_at: dispute_data.created_at, + updated_at: dispute_data.updated_at, + }), None => Err(errors::ConnectorError::WebhookResourceObjectNotFound)?, } } @@ -1210,3 +1210,5 @@ impl self.build_error_response(res, event_builder) } } + +impl ConnectorSpecifications for Braintree {} diff --git a/crates/router/src/connector/braintree/transformers.rs b/crates/router/src/connector/braintree/transformers.rs index 05d8c1c559d6..00624ce0f9b1 100644 --- a/crates/router/src/connector/braintree/transformers.rs +++ b/crates/router/src/connector/braintree/transformers.rs @@ -67,13 +67,11 @@ pub struct GenericVariableInput { #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] - pub struct BraintreeApiErrorResponse { pub api_error_response: ApiErrorResponse, } #[derive(Debug, Deserialize, Serialize)] - pub struct ErrorsObject { pub errors: Vec, @@ -82,7 +80,6 @@ pub struct ErrorsObject { #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] - pub struct TransactionError { pub errors: Vec, pub credit_card: Option, @@ -122,7 +119,6 @@ pub struct BraintreeErrorResponse { #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[serde(untagged)] - pub enum ErrorResponses { BraintreeApiErrorResponse(Box), BraintreeErrorResponse(Box), @@ -305,12 +301,14 @@ impl TryFrom<&BraintreeRouterData<&types::PaymentsAuthorizeRouterData>> | domain::PaymentMethodData::Crypto(_) | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("braintree"), ) @@ -340,6 +338,7 @@ impl TryFrom<&BraintreeRouterData<&types::PaymentsCompleteAuthorizeRouterData>> | api_models::enums::PaymentMethod::BankDebit | api_models::enums::PaymentMethod::Reward | api_models::enums::PaymentMethod::RealTimePayment + | api_models::enums::PaymentMethod::MobilePayment | api_models::enums::PaymentMethod::Upi | api_models::enums::PaymentMethod::OpenBanking | api_models::enums::PaymentMethod::Voucher @@ -438,14 +437,15 @@ impl } else { Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(transaction_data.id), - redirection_data: None, - mandate_reference: transaction_data.payment_method.as_ref().map(|pm| { - MandateReference { + redirection_data: Box::new(None), + mandate_reference: Box::new(transaction_data.payment_method.as_ref().map( + |pm| MandateReference { connector_mandate_id: Some(pm.id.clone().expose()), payment_method_id: None, mandate_metadata: None, - } - }), + connector_mandate_request_reference_id: None, + }, + )), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -463,12 +463,12 @@ impl status: enums::AttemptStatus::AuthenticationPending, response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::NoResponseId, - redirection_data: Some(get_braintree_redirect_form( + redirection_data: Box::new(Some(get_braintree_redirect_form( *client_token_data, item.data.get_payment_method_token()?, item.data.request.payment_method_data.clone(), - )?), - mandate_reference: None, + )?)), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -613,14 +613,15 @@ impl } else { Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(transaction_data.id), - redirection_data: None, - mandate_reference: transaction_data.payment_method.as_ref().map(|pm| { - MandateReference { + redirection_data: Box::new(None), + mandate_reference: Box::new(transaction_data.payment_method.as_ref().map( + |pm| MandateReference { connector_mandate_id: Some(pm.id.clone().expose()), payment_method_id: None, mandate_metadata: None, - } - }), + connector_mandate_request_reference_id: None, + }, + )), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -638,12 +639,12 @@ impl status: enums::AttemptStatus::AuthenticationPending, response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::NoResponseId, - redirection_data: Some(get_braintree_redirect_form( + redirection_data: Box::new(Some(get_braintree_redirect_form( *client_token_data, item.data.get_payment_method_token()?, item.data.request.payment_method_data.clone(), - )?), - mandate_reference: None, + )?)), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -695,14 +696,15 @@ impl } else { Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(transaction_data.id), - redirection_data: None, - mandate_reference: transaction_data.payment_method.as_ref().map(|pm| { - MandateReference { + redirection_data: Box::new(None), + mandate_reference: Box::new(transaction_data.payment_method.as_ref().map( + |pm| MandateReference { connector_mandate_id: Some(pm.id.clone().expose()), payment_method_id: None, mandate_metadata: None, - } - }), + connector_mandate_request_reference_id: None, + }, + )), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -759,14 +761,15 @@ impl } else { Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(transaction_data.id), - redirection_data: None, - mandate_reference: transaction_data.payment_method.as_ref().map(|pm| { - MandateReference { + redirection_data: Box::new(None), + mandate_reference: Box::new(transaction_data.payment_method.as_ref().map( + |pm| MandateReference { connector_mandate_id: Some(pm.id.clone().expose()), payment_method_id: None, mandate_metadata: None, - } - }), + connector_mandate_request_reference_id: None, + }, + )), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -900,7 +903,6 @@ pub struct BraintreeRefundResponseData { } #[derive(Debug, Clone, Deserialize, Serialize)] - pub struct RefundResponse { pub data: BraintreeRefundResponseData, } @@ -1100,11 +1102,13 @@ impl TryFrom<&types::TokenizationRouterData> for BraintreeTokenRequest { | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("braintree"), ) @@ -1272,8 +1276,8 @@ impl TryFrom> } else { Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(transaction_data.id), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -1382,8 +1386,8 @@ impl } else { Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::NoResponseId, - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -1487,8 +1491,8 @@ impl resource_id: types::ResponseId::ConnectorTransactionId( edge_data.node.id.clone(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -1591,6 +1595,9 @@ impl types::PaymentMethodToken::ApplePayDecrypt(_) => Err( unimplemented_payment_method!("Apple Pay", "Simplified", "Braintree"), )?, + types::PaymentMethodToken::PazeDecrypt(_) => { + Err(unimplemented_payment_method!("Paze", "Braintree"))? + } }, transaction: transaction_body, }, @@ -1689,6 +1696,9 @@ fn get_braintree_redirect_form( "Simplified", "Braintree" ))?, + types::PaymentMethodToken::PazeDecrypt(_) => { + Err(unimplemented_payment_method!("Paze", "Braintree"))? + } }, bin: match card_details { domain::PaymentMethodData::Card(card_details) => { @@ -1705,11 +1715,13 @@ fn get_braintree_redirect_form( | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => Err( + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => Err( errors::ConnectorError::NotImplemented("given payment method".to_owned()), )?, }, @@ -1749,7 +1761,7 @@ pub struct BraintreeDisputeData { pub amount_won: Option, pub case_number: Option, pub chargeback_protection_level: Option, - pub currency_iso_code: String, + pub currency_iso_code: enums::Currency, #[serde(default, with = "common_utils::custom_serde::iso8601::option")] pub created_at: Option, pub evidence: Option, diff --git a/crates/router/src/connector/checkout.rs b/crates/router/src/connector/checkout.rs index 3fca0b86aeba..96b30dc80f93 100644 --- a/crates/router/src/connector/checkout.rs +++ b/crates/router/src/connector/checkout.rs @@ -27,7 +27,7 @@ use crate::{ services::{ self, request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, + ConnectorIntegration, ConnectorSpecifications, ConnectorValidation, }, types::{ self, @@ -154,14 +154,16 @@ impl ConnectorCommon for Checkout { } impl ConnectorValidation for Checkout { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { enums::CaptureMethod::Automatic + | enums::CaptureMethod::SequentialAutomatic | enums::CaptureMethod::Manual | enums::CaptureMethod::ManualMultiple => Ok(()), enums::CaptureMethod::Scheduled => Err( @@ -1488,3 +1490,5 @@ impl ConnectorErrorTypeMapping for Checkout { } } } + +impl ConnectorSpecifications for Checkout {} diff --git a/crates/router/src/connector/checkout/transformers.rs b/crates/router/src/connector/checkout/transformers.rs index 223256155cfd..cd758fffb690 100644 --- a/crates/router/src/connector/checkout/transformers.rs +++ b/crates/router/src/connector/checkout/transformers.rs @@ -106,6 +106,7 @@ impl TryFrom<&types::TokenizationRouterData> for TokenRequest { | domain::WalletData::MobilePayRedirect(_) | domain::WalletData::PaypalRedirect(_) | domain::WalletData::PaypalSdk(_) + | domain::WalletData::Paze(_) | domain::WalletData::SamsungPay(_) | domain::WalletData::TwintRedirect {} | domain::WalletData::VippsRedirect {} @@ -128,13 +129,15 @@ impl TryFrom<&types::TokenizationRouterData> for TokenRequest { | domain::PaymentMethodData::MandatePayment | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::CardRedirect(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("checkout"), ) @@ -301,6 +304,9 @@ impl TryFrom<&CheckoutRouterData<&types::PaymentsAuthorizeRouterData>> for Payme types::PaymentMethodToken::ApplePayDecrypt(_) => Err( unimplemented_payment_method!("Apple Pay", "Simplified", "Checkout"), )?, + types::PaymentMethodToken::PazeDecrypt(_) => { + Err(unimplemented_payment_method!("Paze", "Checkout"))? + } }, })), domain::WalletData::ApplePay(_) => { @@ -327,6 +333,9 @@ impl TryFrom<&CheckoutRouterData<&types::PaymentsAuthorizeRouterData>> for Payme }, ))) } + types::PaymentMethodToken::PazeDecrypt(_) => { + Err(unimplemented_payment_method!("Paze", "Checkout"))? + } } } domain::WalletData::AliPayQr(_) @@ -345,6 +354,7 @@ impl TryFrom<&CheckoutRouterData<&types::PaymentsAuthorizeRouterData>> for Payme | domain::WalletData::MobilePayRedirect(_) | domain::WalletData::PaypalRedirect(_) | domain::WalletData::PaypalSdk(_) + | domain::WalletData::Paze(_) | domain::WalletData::SamsungPay(_) | domain::WalletData::TwintRedirect {} | domain::WalletData::VippsRedirect {} @@ -366,13 +376,15 @@ impl TryFrom<&CheckoutRouterData<&types::PaymentsAuthorizeRouterData>> for Payme | domain::PaymentMethodData::MandatePayment | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::CardRedirect(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("checkout"), )) @@ -626,9 +638,11 @@ fn get_connector_meta( capture_method: enums::CaptureMethod, ) -> CustomResult { match capture_method { - enums::CaptureMethod::Automatic => Ok(serde_json::json!(CheckoutMeta { - psync_flow: CheckoutPaymentIntent::Capture, - })), + enums::CaptureMethod::Automatic | enums::CaptureMethod::SequentialAutomatic => { + Ok(serde_json::json!(CheckoutMeta { + psync_flow: CheckoutPaymentIntent::Capture, + })) + } enums::CaptureMethod::Manual | enums::CaptureMethod::ManualMultiple => { Ok(serde_json::json!(CheckoutMeta { psync_flow: CheckoutPaymentIntent::Authorize, @@ -678,8 +692,8 @@ impl TryFrom> }; let payments_response_data = types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()), - redirection_data, - mandate_reference: None, + redirection_data: Box::new(redirection_data), + mandate_reference: Box::new(None), connector_metadata: Some(connector_meta), network_txn_id: None, connector_response_reference_id: Some( @@ -731,8 +745,8 @@ impl TryFrom> }; let payments_response_data = types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()), - redirection_data, - mandate_reference: None, + redirection_data: Box::new(redirection_data), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some( @@ -809,8 +823,8 @@ impl TryFrom> Ok(Self { response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(response.action_id.clone()), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -910,8 +924,8 @@ impl TryFrom> Ok(Self { response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(resource_id), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: Some(connector_meta), network_txn_id: None, connector_response_reference_id: item.response.reference, @@ -1240,7 +1254,7 @@ pub struct CheckoutDisputeWebhookData { pub payment_id: Option, pub action_id: Option, pub amount: i32, - pub currency: String, + pub currency: enums::Currency, #[serde(default, with = "common_utils::custom_serde::iso8601::option")] pub evidence_required_by: Option, pub reason_code: Option, diff --git a/crates/router/src/connector/cybersource.rs b/crates/router/src/connector/cybersource.rs index 806cf67b2da2..078f66335f5c 100644 --- a/crates/router/src/connector/cybersource.rs +++ b/crates/router/src/connector/cybersource.rs @@ -1,9 +1,10 @@ pub mod transformers; -use std::fmt::Debug; - use base64::Engine; -use common_utils::request::RequestContent; +use common_utils::{ + request::RequestContent, + types::{AmountConvertor, MinorUnit, StringMajorUnit, StringMajorUnitForConnector}, +}; use diesel_models::enums; use error_stack::{report, Report, ResultExt}; use masking::{ExposeInterface, PeekInterface}; @@ -12,7 +13,7 @@ use time::OffsetDateTime; use transformers as cybersource; use url::Url; -use super::utils::{PaymentsAuthorizeRequestData, RouterData}; +use super::utils::{convert_amount, PaymentsAuthorizeRequestData, RouterData}; use crate::{ configs::settings, connector::{ @@ -26,7 +27,7 @@ use crate::{ services::{ self, request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, + ConnectorIntegration, ConnectorSpecifications, ConnectorValidation, }, types::{ self, @@ -36,9 +37,18 @@ use crate::{ utils::BytesExt, }; -#[derive(Debug, Clone)] -pub struct Cybersource; +#[derive(Clone)] +pub struct Cybersource { + amount_converter: &'static (dyn AmountConvertor + Sync), +} +impl Cybersource { + pub fn new() -> &'static Self { + &Self { + amount_converter: &StringMajorUnitForConnector, + } + } +} impl Cybersource { pub fn generate_digest(&self, payload: &[u8]) -> String { let payload_digest = digest::digest(&digest::SHA256, payload); @@ -236,14 +246,17 @@ impl ConnectorCommon for Cybersource { } impl ConnectorValidation for Cybersource { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( connector_utils::construct_not_implemented_error_report(capture_method, self.id()), ), @@ -617,20 +630,20 @@ impl req: &types::PaymentsPreProcessingRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_router_data = cybersource::CybersourceRouterData::try_from(( - &self.get_currency_unit(), + let minor_amount = req.request - .currency + .minor_amount .ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "currency", - })?, + field_name: "minor_amount", + })?; + let currency = req.request - .amount + .currency .ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "amount", - })?, - req, - ))?; + field_name: "currency", + })?; + let amount = convert_amount(self.amount_converter, minor_amount, currency)?; + let connector_router_data = cybersource::CybersourceRouterData::from((amount, req)); let connector_req = cybersource::CybersourcePreProcessingRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -718,12 +731,12 @@ impl ConnectorIntegration CustomResult { - let connector_router_data = cybersource::CybersourceRouterData::try_from(( - &self.get_currency_unit(), + let amount = convert_amount( + self.amount_converter, + req.request.minor_amount_to_capture, req.request.currency, - req.request.amount_to_capture, - req, - ))?; + )?; + let connector_router_data = cybersource::CybersourceRouterData::from((amount, req)); let connector_req = cybersource::CybersourcePaymentsCaptureRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -922,12 +935,12 @@ impl ConnectorIntegration CustomResult { - let connector_router_data = cybersource::CybersourceRouterData::try_from(( - &self.get_currency_unit(), + let amount = convert_amount( + self.amount_converter, + req.request.minor_amount, req.request.currency, - req.request.amount, - req, - ))?; + )?; + let connector_router_data = cybersource::CybersourceRouterData::from((amount, req)); if req.is_three_ds() && req.request.is_card() && (req.request.connector_mandate_id().is_none() @@ -1068,12 +1081,12 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_router_data = cybersource::CybersourceRouterData::try_from(( - &self.get_currency_unit(), + let amount = convert_amount( + self.amount_converter, + req.request.minor_amount, req.request.destination_currency, - req.request.amount, - req, - ))?; + )?; + let connector_router_data = cybersource::CybersourceRouterData::from((amount, req)); let connector_req = cybersource::CybersourcePayoutFulfillRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -1193,12 +1206,12 @@ impl req: &types::PaymentsCompleteAuthorizeRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_router_data = cybersource::CybersourceRouterData::try_from(( - &self.get_currency_unit(), + let amount = convert_amount( + self.amount_converter, + req.request.minor_amount, req.request.currency, - req.request.amount, - req, - ))?; + )?; + let connector_router_data = cybersource::CybersourceRouterData::from((amount, req)); let connector_req = cybersource::CybersourcePaymentsRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -1317,20 +1330,21 @@ impl ConnectorIntegration CustomResult { - let connector_router_data = cybersource::CybersourceRouterData::try_from(( - &self.get_currency_unit(), + let minor_amount = req.request - .currency + .minor_amount .ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "Currency", - })?, + field_name: "Amount", + })?; + let currency = req.request - .amount + .currency .ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "Amount", - })?, - req, - ))?; + field_name: "Currency", + })?; + let amount = convert_amount(self.amount_converter, minor_amount, currency)?; + let connector_router_data = cybersource::CybersourceRouterData::from((amount, req)); + let connector_req = cybersource::CybersourceVoidRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -1443,12 +1457,12 @@ impl ConnectorIntegration CustomResult { - let connector_router_data = cybersource::CybersourceRouterData::try_from(( - &self.get_currency_unit(), + let refund_amount = convert_amount( + self.amount_converter, + req.request.minor_refund_amount, req.request.currency, - req.request.refund_amount, - req, - ))?; + )?; + let connector_router_data = cybersource::CybersourceRouterData::from((refund_amount, req)); let connector_req = cybersource::CybersourceRefundRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -1609,12 +1623,14 @@ impl req: &types::PaymentsIncrementalAuthorizationRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_router_data = cybersource::CybersourceRouterData::try_from(( - &self.get_currency_unit(), + let minor_additional_amount = MinorUnit::new(req.request.additional_amount); + let additional_amount = convert_amount( + self.amount_converter, + minor_additional_amount, req.request.currency, - req.request.additional_amount, - req, - ))?; + )?; + let connector_router_data = + cybersource::CybersourceRouterData::from((additional_amount, req)); let connector_request = cybersource::CybersourcePaymentsIncrementalAuthorizationRequest::try_from( &connector_router_data, @@ -1703,3 +1719,5 @@ impl api::IncomingWebhook for Cybersource { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for Cybersource {} diff --git a/crates/router/src/connector/cybersource/transformers.rs b/crates/router/src/connector/cybersource/transformers.rs index 864f77b55ddc..7ce98d3d5a34 100644 --- a/crates/router/src/connector/cybersource/transformers.rs +++ b/crates/router/src/connector/cybersource/transformers.rs @@ -1,18 +1,16 @@ use api_models::payments; #[cfg(feature = "payouts")] -use api_models::{ - payments::{AddressDetails, PhoneDetails}, - payouts::PayoutMethodData, -}; +use api_models::payouts::PayoutMethodData; use base64::Engine; use common_enums::FutureUsage; use common_utils::{ ext_traits::{OptionExt, ValueExt}, pii, - types::SemanticVersion, + types::{SemanticVersion, StringMajorUnit}, }; use error_stack::ResultExt; -use josekit::jwt::decode_header; +#[cfg(feature = "payouts")] +use hyperswitch_domain_models::address::{AddressDetails, PhoneDetails}; use masking::{ExposeInterface, PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -42,21 +40,16 @@ use crate::{ #[derive(Debug, Serialize)] pub struct CybersourceRouterData { - pub amount: String, + pub amount: StringMajorUnit, pub router_data: T, } -impl TryFrom<(&api::CurrencyUnit, enums::Currency, i64, T)> for CybersourceRouterData { - type Error = error_stack::Report; - fn try_from( - (currency_unit, currency, amount, item): (&api::CurrencyUnit, enums::Currency, i64, T), - ) -> Result { - // This conversion function is used at different places in the file, if updating this, keep a check for those - let amount = utils::get_amount_as_string(currency_unit, amount, currency)?; - Ok(Self { +impl From<(StringMajorUnit, T)> for CybersourceRouterData { + fn from((amount, router_data): (StringMajorUnit, T)) -> Self { + Self { amount, - router_data: item, - }) + router_data, + } } } @@ -94,7 +87,7 @@ impl TryFrom<&types::SetupMandateRouterData> for CybersourceZeroMandateRequest { let order_information = OrderInformationWithBill { amount_details: Amount { - total_amount: "0".to_string(), + total_amount: StringMajorUnit::zero(), currency: item.request.currency, }, bill_to: Some(bill_to), @@ -126,10 +119,13 @@ impl TryFrom<&types::SetupMandateRouterData> for CybersourceZeroMandateRequest { let (payment_information, solution) = match item.request.payment_method_data.clone() { domain::PaymentMethodData::Card(ccard) => { - let card_issuer = ccard.get_card_issuer(); - let card_type = match card_issuer { - Ok(issuer) => Some(String::from(issuer)), - Err(_) => None, + let card_type = match ccard + .card_network + .clone() + .and_then(get_cybersource_card_type) + { + Some(card_network) => Some(card_network.to_string()), + None => ccard.get_card_issuer().ok().map(String::from), }; ( PaymentInformation::Cards(Box::new(CardPaymentInformation { @@ -173,6 +169,9 @@ impl TryFrom<&types::SetupMandateRouterData> for CybersourceZeroMandateRequest { types::PaymentMethodToken::Token(_) => Err( unimplemented_payment_method!("Apple Pay", "Manual", "Cybersource"), )?, + types::PaymentMethodToken::PazeDecrypt(_) => { + Err(unimplemented_payment_method!("Paze", "Cybersource"))? + } }, None => ( PaymentInformation::ApplePayToken(Box::new( @@ -218,6 +217,7 @@ impl TryFrom<&types::SetupMandateRouterData> for CybersourceZeroMandateRequest { | domain::WalletData::MobilePayRedirect(_) | domain::WalletData::PaypalRedirect(_) | domain::WalletData::PaypalSdk(_) + | domain::WalletData::Paze(_) | domain::WalletData::SamsungPay(_) | domain::WalletData::TwintRedirect {} | domain::WalletData::VippsRedirect {} @@ -239,12 +239,14 @@ impl TryFrom<&types::SetupMandateRouterData> for CybersourceZeroMandateRequest { | domain::PaymentMethodData::MandatePayment | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Cybersource"), ))? @@ -518,14 +520,14 @@ pub struct OrderInformation { #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct Amount { - total_amount: String, + total_amount: StringMajorUnit, currency: api_models::enums::Currency, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct AdditionalAmount { - additional_amount: String, + additional_amount: StringMajorUnit, currency: String, } @@ -611,8 +613,8 @@ impl PaymentSolution::ApplePay | PaymentSolution::SamsungPay => network .as_ref() .map(|card_network| match card_network.to_lowercase().as_str() { - "amex" => "aesk", - "discover" => "dipb", + "amex" => "internet", + "discover" => "internet", "mastercard" => "spa", "visa" => "internet", _ => "internet", @@ -1094,7 +1096,7 @@ impl // } fn build_bill_to( - address_details: Option<&payments::Address>, + address_details: Option<&hyperswitch_domain_models::address::Address>, email: pii::Email, ) -> Result> { let default_address = BillTo { @@ -1110,12 +1112,16 @@ fn build_bill_to( Ok(address_details .and_then(|addr| { addr.address.as_ref().map(|addr| BillTo { - first_name: addr.first_name.clone(), - last_name: addr.last_name.clone(), - address1: addr.line1.clone(), - locality: addr.city.clone(), - administrative_area: addr.to_state_code_as_optional().ok().flatten(), - postal_code: addr.zip.clone(), + first_name: addr.first_name.remove_new_line(), + last_name: addr.last_name.remove_new_line(), + address1: addr.line1.remove_new_line(), + locality: addr.city.remove_new_line(), + administrative_area: addr + .to_state_code_as_optional() + .ok() + .flatten() + .remove_new_line(), + postal_code: addr.zip.remove_new_line(), country: addr.country, email, }) @@ -1161,10 +1167,13 @@ impl let bill_to = build_bill_to(item.router_data.get_optional_billing(), email)?; let order_information = OrderInformationWithBill::from((item, Some(bill_to))); - let card_issuer = ccard.get_card_issuer(); - let card_type = match card_issuer { - Ok(issuer) => Some(String::from(issuer)), - Err(_) => None, + let card_type = match ccard + .card_network + .clone() + .and_then(get_cybersource_card_type) + { + Some(card_network) => Some(card_network.to_string()), + None => ccard.get_card_issuer().ok().map(String::from), }; let security_code = if item @@ -1235,6 +1244,89 @@ impl } } +impl + TryFrom<( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + hyperswitch_domain_models::payment_method_data::CardDetailsForNetworkTransactionId, + )> for CybersourcePaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + (item, ccard): ( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + hyperswitch_domain_models::payment_method_data::CardDetailsForNetworkTransactionId, + ), + ) -> Result { + let email = item + .router_data + .get_billing_email() + .or(item.router_data.request.get_email())?; + let bill_to = build_bill_to(item.router_data.get_optional_billing(), email)?; + let order_information = OrderInformationWithBill::from((item, Some(bill_to))); + + let card_issuer = ccard.get_card_issuer(); + let card_type = match card_issuer { + Ok(issuer) => Some(String::from(issuer)), + Err(_) => None, + }; + + let payment_information = PaymentInformation::Cards(Box::new(CardPaymentInformation { + card: Card { + number: ccard.card_number, + expiration_month: ccard.card_exp_month, + expiration_year: ccard.card_exp_year, + security_code: None, + card_type: card_type.clone(), + }, + })); + + let processing_information = ProcessingInformation::try_from((item, None, card_type))?; + let client_reference_information = ClientReferenceInformation::from(item); + let merchant_defined_information = item + .router_data + .request + .metadata + .clone() + .map(Vec::::foreign_from); + + let consumer_authentication_information = item + .router_data + .request + .authentication_data + .as_ref() + .map(|authn_data| { + let (ucaf_authentication_data, cavv) = + if ccard.card_network == Some(common_enums::CardNetwork::Mastercard) { + (Some(Secret::new(authn_data.cavv.clone())), None) + } else { + (None, Some(authn_data.cavv.clone())) + }; + CybersourceConsumerAuthInformation { + ucaf_collection_indicator: None, + cavv, + ucaf_authentication_data, + xid: Some(authn_data.threeds_server_transaction_id.clone()), + directory_server_transaction_id: authn_data + .ds_trans_id + .clone() + .map(Secret::new), + specification_version: None, + pa_specification_version: Some(authn_data.message_version.clone()), + veres_enrolled: Some("Y".to_string()), + } + }); + + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + consumer_authentication_information, + merchant_defined_information, + }) + } +} + impl TryFrom<( &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, @@ -1316,6 +1408,92 @@ impl } } +impl + TryFrom<( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + Box, + )> for CybersourcePaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + (item, paze_data): ( + &CybersourceRouterData<&types::PaymentsAuthorizeRouterData>, + Box, + ), + ) -> Result { + let email = item.router_data.request.get_email()?; + let (first_name, last_name) = match paze_data.billing_address.name { + Some(name) => { + let (first_name, last_name) = name + .peek() + .split_once(' ') + .map(|(first, last)| (first.to_string(), last.to_string())) + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "billing_address.name", + })?; + (Secret::from(first_name), Secret::from(last_name)) + } + None => ( + item.router_data.get_billing_first_name()?, + item.router_data.get_billing_last_name()?, + ), + }; + let bill_to = BillTo { + first_name: Some(first_name), + last_name: Some(last_name), + address1: paze_data.billing_address.line1, + locality: paze_data.billing_address.city.map(|city| city.expose()), + administrative_area: Some(Secret::from( + //Paze wallet is currently supported in US only + common_enums::UsStatesAbbreviation::foreign_try_from( + paze_data + .billing_address + .state + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "billing_address.state", + })? + .peek() + .to_owned(), + )? + .to_string(), + )), + postal_code: paze_data.billing_address.zip, + country: paze_data.billing_address.country_code, + email, + }; + let order_information = OrderInformationWithBill::from((item, Some(bill_to))); + + let payment_information = + PaymentInformation::NetworkToken(Box::new(NetworkTokenPaymentInformation { + tokenized_card: NetworkTokenizedCard { + number: paze_data.token.payment_token, + expiration_month: paze_data.token.token_expiration_month, + expiration_year: paze_data.token.token_expiration_year, + cryptogram: Some(paze_data.token.payment_account_reference), + transaction_type: "1".to_string(), + }, + })); + + let processing_information = ProcessingInformation::try_from((item, None, None))?; + let client_reference_information = ClientReferenceInformation::from(item); + let merchant_defined_information = item + .router_data + .request + .metadata + .clone() + .map(Vec::::foreign_from); + + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + consumer_authentication_information: None, + merchant_defined_information, + }) + } +} + impl TryFrom<( &CybersourceRouterData<&types::PaymentsCompleteAuthorizeRouterData>, @@ -1336,10 +1514,13 @@ impl let bill_to = build_bill_to(item.router_data.get_optional_billing(), email)?; let order_information = OrderInformationWithBill::from((item, bill_to)); - let card_issuer = ccard.get_card_issuer(); - let card_type = match card_issuer { - Ok(issuer) => Some(String::from(issuer)), - Err(_) => None, + let card_type = match ccard + .card_network + .clone() + .and_then(get_cybersource_card_type) + { + Some(card_network) => Some(card_network.to_string()), + None => ccard.get_card_issuer().ok().map(String::from), }; let payment_information = PaymentInformation::Cards(Box::new(CardPaymentInformation { @@ -1567,7 +1748,7 @@ impl let processing_information = ProcessingInformation::try_from(( item, Some(PaymentSolution::SamsungPay), - Some(samsung_pay_data.payment_credential.card_brand), + Some(samsung_pay_data.payment_credential.card_brand.to_string()), ))?; let client_reference_information = ClientReferenceInformation::from(item); let merchant_defined_information = item @@ -1591,9 +1772,10 @@ impl fn get_samsung_pay_fluid_data_value( samsung_pay_token_data: &hyperswitch_domain_models::payment_method_data::SamsungPayTokenData, ) -> Result> { - let samsung_pay_header = decode_header(samsung_pay_token_data.data.clone().peek()) - .change_context(errors::ConnectorError::RequestEncodingFailed) - .attach_printable("Failed to decode samsung pay header")?; + let samsung_pay_header = + josekit::jwt::decode_header(samsung_pay_token_data.data.clone().peek()) + .change_context(errors::ConnectorError::RequestEncodingFailed) + .attach_printable("Failed to decode samsung pay header")?; let samsung_pay_kid_optional = samsung_pay_header.claim("kid").and_then(|kid| kid.as_str()); @@ -1636,6 +1818,9 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> "Cybersource" ))? } + types::PaymentMethodToken::PazeDecrypt(_) => { + Err(unimplemented_payment_method!("Paze", "Cybersource"))? + } }, None => { let email = item @@ -1710,6 +1895,19 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> domain::WalletData::SamsungPay(samsung_pay_data) => { Self::try_from((item, samsung_pay_data)) } + domain::WalletData::Paze(_) => { + match item.router_data.payment_method_token.clone() { + Some(types::PaymentMethodToken::PazeDecrypt( + paze_decrypted_data, + )) => Self::try_from((item, paze_decrypted_data)), + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message( + "Cybersource", + ), + ) + .into()), + } + } domain::WalletData::AliPayQr(_) | domain::WalletData::AliPayRedirect(_) | domain::WalletData::AliPayHkRedirect(_) @@ -1756,6 +1954,9 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> domain::PaymentMethodData::NetworkToken(token_data) => { Self::try_from((item, token_data)) } + domain::PaymentMethodData::CardDetailsForNetworkTransactionId(card) => { + Self::try_from((item, card)) + } domain::PaymentMethodData::CardRedirect(_) | domain::PaymentMethodData::PayLater(_) | domain::PaymentMethodData::BankRedirect(_) @@ -1764,6 +1965,7 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> | domain::PaymentMethodData::Crypto(_) | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::GiftCard(_) @@ -1841,10 +2043,13 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> ) -> Result { match item.router_data.request.payment_method_data.clone() { domain::PaymentMethodData::Card(ccard) => { - let card_issuer = ccard.get_card_issuer(); - let card_type = match card_issuer { - Ok(issuer) => Some(String::from(issuer)), - Err(_) => None, + let card_type = match ccard + .card_network + .clone() + .and_then(get_cybersource_card_type) + { + Some(card_network) => Some(card_network.to_string()), + None => ccard.get_card_issuer().ok().map(String::from), }; let payment_information = PaymentInformation::Cards(Box::new(CardPaymentInformation { @@ -1872,12 +2077,14 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> | domain::PaymentMethodData::MandatePayment | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Cybersource"), ) @@ -2349,12 +2556,13 @@ fn get_payment_response( .map(|payment_instrument| payment_instrument.id.expose()), payment_method_id: None, mandate_metadata: None, + connector_mandate_request_reference_id: None, }); Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(info_response.id.clone()), - redirection_data: None, - mandate_reference, + redirection_data: Box::new(None), + mandate_reference: Box::new(mandate_reference), connector_metadata: None, network_txn_id: info_response.processor_information.as_ref().and_then( |processor_information| processor_information.network_transaction_id.clone(), @@ -2441,18 +2649,20 @@ impl status: enums::AttemptStatus::AuthenticationPending, response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::NoResponseId, - redirection_data: Some(services::RedirectForm::CybersourceAuthSetup { - access_token: info_response - .consumer_authentication_information - .access_token, - ddc_url: info_response - .consumer_authentication_information - .device_data_collection_url, - reference_id: info_response - .consumer_authentication_information - .reference_id, - }), - mandate_reference: None, + redirection_data: Box::new(Some( + services::RedirectForm::CybersourceAuthSetup { + access_token: info_response + .consumer_authentication_information + .access_token, + ddc_url: info_response + .consumer_authentication_information + .device_data_collection_url, + reference_id: info_response + .consumer_authentication_information + .reference_id, + }, + )), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some( @@ -2565,10 +2775,13 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsPreProcessingRouterData>> )?; let payment_information = match payment_method_data { domain::PaymentMethodData::Card(ccard) => { - let card_issuer = ccard.get_card_issuer(); - let card_type = match card_issuer { - Ok(issuer) => Some(String::from(issuer)), - Err(_) => None, + let card_type = match ccard + .card_network + .clone() + .and_then(get_cybersource_card_type) + { + Some(card_network) => Some(card_network.to_string()), + None => ccard.get_card_issuer().ok().map(String::from), }; Ok(PaymentInformation::Cards(Box::new( CardPaymentInformation { @@ -2592,12 +2805,14 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsPreProcessingRouterData>> | domain::PaymentMethodData::MandatePayment | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Cybersource"), )) @@ -2706,12 +2921,14 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsCompleteAuthorizeRouterData> | domain::PaymentMethodData::MandatePayment | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Cybersource"), ) @@ -2855,8 +3072,8 @@ impl status, response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::NoResponseId, - redirection_data, - mandate_reference: None, + redirection_data: Box::new(redirection_data), + mandate_reference: Box::new(None), connector_metadata: Some(serde_json::json!({ "three_ds_data": three_ds_data })), @@ -3070,6 +3287,7 @@ impl .map(|payment_instrument| payment_instrument.id.expose()), payment_method_id: None, mandate_metadata: None, + connector_mandate_request_reference_id: None, }); let mut mandate_status = enums::AttemptStatus::foreign_from(( item.response @@ -3100,8 +3318,8 @@ impl resource_id: types::ResponseId::ConnectorTransactionId( item.response.id.clone(), ), - redirection_data: None, - mandate_reference, + redirection_data: Box::new(None), + mandate_reference: Box::new(mandate_reference), connector_metadata: None, network_txn_id: item.response.processor_information.as_ref().and_then( |processor_information| { @@ -3238,8 +3456,8 @@ impl resource_id: types::ResponseId::ConnectorTransactionId( item.response.id.clone(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: item @@ -3260,8 +3478,8 @@ impl resource_id: types::ResponseId::ConnectorTransactionId( item.response.id.clone(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(item.response.id), @@ -3823,3 +4041,38 @@ pub fn get_error_reason( (None, None, None) => None, } } + +fn get_cybersource_card_type(card_network: common_enums::CardNetwork) -> Option<&'static str> { + match card_network { + common_enums::CardNetwork::Visa => Some("001"), + common_enums::CardNetwork::Mastercard => Some("002"), + common_enums::CardNetwork::AmericanExpress => Some("003"), + common_enums::CardNetwork::JCB => Some("007"), + common_enums::CardNetwork::DinersClub => Some("005"), + common_enums::CardNetwork::Discover => Some("004"), + common_enums::CardNetwork::CartesBancaires => Some("006"), + common_enums::CardNetwork::UnionPay => Some("062"), + //"042" is the type code for Masetro Cards(International). For Maestro Cards(UK-Domestic) the mapping should be "024" + common_enums::CardNetwork::Maestro => Some("042"), + common_enums::CardNetwork::Interac | common_enums::CardNetwork::RuPay => None, + } +} + +pub trait RemoveNewLine { + fn remove_new_line(&self) -> Self; +} + +impl RemoveNewLine for Option> { + fn remove_new_line(&self) -> Self { + self.clone().map(|masked_value| { + let new_string = masked_value.expose().replace("\n", " "); + Secret::new(new_string) + }) + } +} + +impl RemoveNewLine for Option { + fn remove_new_line(&self) -> Self { + self.clone().map(|value| value.replace("\n", " ")) + } +} diff --git a/crates/router/src/connector/dummyconnector.rs b/crates/router/src/connector/dummyconnector.rs index 13a05578238b..15a2ff690366 100644 --- a/crates/router/src/connector/dummyconnector.rs +++ b/crates/router/src/connector/dummyconnector.rs @@ -16,7 +16,7 @@ use crate::{ services::{ self, request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, + ConnectorIntegration, ConnectorSpecifications, ConnectorValidation, }, types::{ self, @@ -124,14 +124,17 @@ impl ConnectorCommon for DummyConnector { } impl ConnectorValidation for DummyConnector { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( connector_utils::construct_not_supported_error_report(capture_method, self.id()), ), @@ -611,3 +614,5 @@ impl api::IncomingWebhook for DummyConnector { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for DummyConnector {} diff --git a/crates/router/src/connector/dummyconnector/transformers.rs b/crates/router/src/connector/dummyconnector/transformers.rs index 2cf5960bf753..79caa3d0a767 100644 --- a/crates/router/src/connector/dummyconnector/transformers.rs +++ b/crates/router/src/connector/dummyconnector/transformers.rs @@ -253,8 +253,8 @@ impl TryFrom, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual - | enums::CaptureMethod::ManualMultiple => Ok(()), + | enums::CaptureMethod::ManualMultiple + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::Scheduled => Err( connector_utils::construct_not_implemented_error_report(capture_method, self.id()), ), @@ -1042,3 +1044,5 @@ impl services::ConnectorRedirectResponse for Globalpay { } } } + +impl ConnectorSpecifications for Globalpay {} diff --git a/crates/router/src/connector/globalpay/requests.rs b/crates/router/src/connector/globalpay/requests.rs index 9ccfcd90be38..223be54d3fd5 100644 --- a/crates/router/src/connector/globalpay/requests.rs +++ b/crates/router/src/connector/globalpay/requests.rs @@ -99,7 +99,6 @@ pub struct GlobalpayRefreshTokenRequest { } #[derive(Debug, Serialize, Deserialize)] - pub struct CurrencyConversion { /// A unique identifier generated by Global Payments to identify the currency conversion. It /// can be used to reference a currency conversion when processing a sale or a refund @@ -108,7 +107,6 @@ pub struct CurrencyConversion { } #[derive(Debug, Serialize, Deserialize)] - pub struct Device { pub capabilities: Option, @@ -128,7 +126,6 @@ pub struct Device { } #[derive(Debug, Serialize, Deserialize)] - pub struct Capabilities { pub authorization_modes: Option>, /// The number of lines that can be used to display information on the device. @@ -146,7 +143,6 @@ pub struct Capabilities { } #[derive(Debug, Serialize, Deserialize)] - pub struct Lodging { /// A reference that identifies the booking reference for a lodging stay. pub booking_reference: Option, @@ -163,7 +159,6 @@ pub struct Lodging { } #[derive(Debug, Serialize, Deserialize)] - pub struct LodgingChargeItem { pub payment_method_program_codes: Option>, /// A reference that identifies the charge item, such as a lodging folio number. @@ -195,7 +190,6 @@ pub struct Notifications { } #[derive(Debug, Serialize, Deserialize)] - pub struct Order { /// Merchant defined field common to all transactions that are part of the same order. pub reference: Option, @@ -239,7 +233,6 @@ pub struct PaymentMethod { } #[derive(Debug, Serialize, Deserialize)] - pub struct Apm { /// A string used to identify the payment method provider being used to execute this /// transaction. @@ -247,9 +240,7 @@ pub struct Apm { } /// Information outlining the degree of authentication executed related to a transaction. - #[derive(Debug, Serialize, Deserialize)] - pub struct Authentication { /// Information outlining the degree of 3D Secure authentication executed. pub three_ds: Option, @@ -259,9 +250,7 @@ pub struct Authentication { } /// Information outlining the degree of 3D Secure authentication executed. - #[derive(Debug, Serialize, Deserialize)] - pub struct ThreeDs { /// The reference created by the 3DSecure Directory Server to identify the specific /// authentication attempt. @@ -284,7 +273,6 @@ pub struct ThreeDs { } #[derive(Debug, Serialize, Deserialize)] - pub struct BankTransfer { /// The number or reference for the payer's bank account. pub account_number: Option>, @@ -298,7 +286,6 @@ pub struct BankTransfer { pub sec_code: Option, } #[derive(Debug, Serialize, Deserialize)] - pub struct Bank { pub address: Option
, /// The local identifier code for the bank. @@ -401,7 +388,6 @@ pub enum AuthorizationMode { /// requested amount. /// pub example: PARTIAL /// - /// /// Describes whether the device can process partial authorizations. Partial, } @@ -428,7 +414,6 @@ pub enum CaptureMode { /// Describes whether the transaction was processed in a face to face(CP) scenario or a /// Customer Not Present (CNP) scenario. - #[derive(Debug, Default, Serialize, Deserialize)] pub enum Channel { #[default] @@ -609,7 +594,6 @@ pub enum NumberType { } /// Indicates how the transaction was authorized by the merchant. - #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum SecCode { @@ -766,7 +750,6 @@ pub enum CardStorageMode { /// Indicates the transaction processing model being executed when using stored /// credentials. - #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum Model { diff --git a/crates/router/src/connector/globalpay/transformers.rs b/crates/router/src/connector/globalpay/transformers.rs index 19568ae4671f..8008cc309c27 100644 --- a/crates/router/src/connector/globalpay/transformers.rs +++ b/crates/router/src/connector/globalpay/transformers.rs @@ -262,6 +262,7 @@ fn get_payment_response( connector_mandate_id: Some(id.expose()), payment_method_id: None, mandate_metadata: None, + connector_mandate_request_reference_id: None, }) }); match status { @@ -274,8 +275,8 @@ fn get_payment_response( }), _ => Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(response.id), - redirection_data, - mandate_reference, + redirection_data: Box::new(redirection_data), + mandate_reference: Box::new(mandate_reference), connector_metadata: None, network_txn_id: None, connector_response_reference_id: response.reference, @@ -472,7 +473,7 @@ fn get_mandate_details(item: &types::PaymentsAuthorizeRouterData) -> Result connector_mandate_ids.connector_mandate_id, + )) => connector_mandate_ids.get_connector_mandate_id(), _ => None, } }); diff --git a/crates/router/src/connector/gpayments.rs b/crates/router/src/connector/gpayments.rs index 9f88e215138f..1febbfa7885d 100644 --- a/crates/router/src/connector/gpayments.rs +++ b/crates/router/src/connector/gpayments.rs @@ -14,7 +14,7 @@ use crate::{ core::errors::{self, CustomResult}, events::connector_api_logs::ConnectorEvent, headers, services, - services::{request, ConnectorIntegration, ConnectorValidation}, + services::{request, ConnectorIntegration, ConnectorSpecifications, ConnectorValidation}, types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, @@ -613,3 +613,5 @@ impl self.build_error_response(res, event_builder) } } + +impl ConnectorSpecifications for Gpayments {} diff --git a/crates/router/src/connector/iatapay.rs b/crates/router/src/connector/iatapay.rs index cf4a1d6e16bc..e694968ee04b 100644 --- a/crates/router/src/connector/iatapay.rs +++ b/crates/router/src/connector/iatapay.rs @@ -22,7 +22,7 @@ use crate::{ services::{ self, request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, + ConnectorIntegration, ConnectorSpecifications, ConnectorValidation, }, types::{ self, @@ -757,3 +757,5 @@ impl api::IncomingWebhook for Iatapay { } } } + +impl ConnectorSpecifications for Iatapay {} diff --git a/crates/router/src/connector/iatapay/transformers.rs b/crates/router/src/connector/iatapay/transformers.rs index fde80abd259b..641505780b46 100644 --- a/crates/router/src/connector/iatapay/transformers.rs +++ b/crates/router/src/connector/iatapay/transformers.rs @@ -6,9 +6,7 @@ use masking::{Secret, SwitchStrategy}; use serde::{Deserialize, Serialize}; use crate::{ - connector::utils::{ - self as connector_util, PaymentsAuthorizeRequestData, RefundsRequestData, RouterData, - }, + connector::utils::{self as connector_util, PaymentsAuthorizeRequestData, RefundsRequestData}, consts, core::errors, services, @@ -85,7 +83,7 @@ pub struct PayerInfo { #[derive(Debug, Serialize)] #[serde(rename_all = "UPPERCASE")] pub enum PreferredCheckoutMethod { - Vpa, + Vpa, //Passing this in UPI_COLLECT will trigger an S2S payment call which is not required. Qr, } @@ -102,6 +100,7 @@ pub struct IatapayPaymentsRequest { notification_url: String, #[serde(skip_serializing_if = "Option::is_none")] payer_info: Option, + #[serde(skip_serializing_if = "Option::is_none")] preferred_checkout_method: Option, } @@ -127,7 +126,7 @@ impl >, >, ) -> Result { - let return_url = item.router_data.get_return_url()?; + let return_url = item.router_data.request.get_router_return_url()?; // Iatapay processes transactions through the payment method selected based on the country let (country, payer_info, preferred_checkout_method) = match item.router_data.request.payment_method_data.clone() { @@ -137,7 +136,7 @@ impl upi_data.vpa_id.map(|id| PayerInfo { token_id: id.switch_strategy(), }), - Some(PreferredCheckoutMethod::Vpa), + None, ), domain::UpiData::UpiIntent(_) => ( common_enums::CountryAlpha2::IN, @@ -202,11 +201,13 @@ impl | domain::PaymentMethodData::Crypto(_) | domain::PaymentMethodData::MandatePayment | domain::PaymentMethodData::Reward + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::CardToken(_) | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( connector_util::get_unimplemented_payment_method_error_message("iatapay"), ))? @@ -380,8 +381,8 @@ fn get_iatpay_response( types::PaymentsResponseData::TransactionResponse { resource_id: id, - redirection_data, - mandate_reference: None, + redirection_data: Box::new(redirection_data), + mandate_reference: Box::new(None), connector_metadata, network_txn_id: None, connector_response_reference_id: connector_response_reference_id.clone(), @@ -391,8 +392,8 @@ fn get_iatpay_response( } None => types::PaymentsResponseData::TransactionResponse { resource_id: id.clone(), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: connector_response_reference_id.clone(), diff --git a/crates/router/src/connector/itaubank.rs b/crates/router/src/connector/itaubank.rs index 0c99a95e0e51..2087c72ba859 100644 --- a/crates/router/src/connector/itaubank.rs +++ b/crates/router/src/connector/itaubank.rs @@ -14,7 +14,7 @@ use crate::{ core::errors::{self, CustomResult}, events::connector_api_logs::ConnectorEvent, headers, - services::{self, request, ConnectorIntegration, ConnectorValidation}, + services::{self, request, ConnectorIntegration, ConnectorSpecifications, ConnectorValidation}, types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, @@ -785,3 +785,5 @@ impl api::IncomingWebhook for Itaubank { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for Itaubank {} diff --git a/crates/router/src/connector/itaubank/transformers.rs b/crates/router/src/connector/itaubank/transformers.rs index 0cd070c20537..127f8526afe4 100644 --- a/crates/router/src/connector/itaubank/transformers.rs +++ b/crates/router/src/connector/itaubank/transformers.rs @@ -115,12 +115,14 @@ impl TryFrom<&ItaubankRouterData<&types::PaymentsAuthorizeRouterData>> for Itaub | domain::PaymentMethodData::MandatePayment | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::CardToken(_) | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( "Selected payment method through itaubank".to_string(), ) @@ -279,8 +281,8 @@ impl resource_id: types::ResponseId::ConnectorTransactionId( item.response.txid.to_owned(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata, network_txn_id: None, connector_response_reference_id: Some(item.response.txid), @@ -367,8 +369,8 @@ impl resource_id: types::ResponseId::ConnectorTransactionId( item.response.txid.to_owned(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata, network_txn_id: None, connector_response_reference_id: Some(item.response.txid), diff --git a/crates/router/src/connector/klarna.rs b/crates/router/src/connector/klarna.rs index 0e6fab805848..4e54311a89a7 100644 --- a/crates/router/src/connector/klarna.rs +++ b/crates/router/src/connector/klarna.rs @@ -1,9 +1,11 @@ pub mod transformers; -use std::fmt::Debug; use api_models::enums; use base64::Engine; -use common_utils::request::RequestContent; +use common_utils::{ + request::RequestContent, + types::{AmountConvertor, MinorUnit, MinorUnitForConnector}, +}; use error_stack::{report, ResultExt}; use masking::PeekInterface; use router_env::logger; @@ -19,7 +21,7 @@ use crate::{ services::{ self, request::{self, Mask}, - ConnectorValidation, + ConnectorSpecifications, ConnectorValidation, }, types::{ self, @@ -29,8 +31,18 @@ use crate::{ utils::BytesExt, }; -#[derive(Debug, Clone)] -pub struct Klarna; +#[derive(Clone)] +pub struct Klarna { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl Klarna { + pub fn new() -> &'static Self { + &Self { + amount_converter: &MinorUnitForConnector, + } + } +} impl ConnectorCommon for Klarna { fn id(&self) -> &'static str { @@ -98,14 +110,17 @@ impl ConnectorCommon for Klarna { } impl ConnectorValidation for Klarna { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( connector_utils::construct_not_supported_error_report(capture_method, self.id()), ), @@ -215,12 +230,12 @@ impl req: &types::PaymentsSessionRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_router_data = klarna::KlarnaRouterData::try_from(( - &self.get_currency_unit(), + let amount = connector_utils::convert_amount( + self.amount_converter, + req.request.minor_amount, req.request.currency, - req.request.amount, - req, - ))?; + )?; + let connector_router_data = klarna::KlarnaRouterData::from((amount, req)); let connector_req = klarna::KlarnaSessionRequest::try_from(&connector_router_data)?; // encode only for for urlencoded things. @@ -342,12 +357,12 @@ impl req: &types::PaymentsCaptureRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_router_data = klarna::KlarnaRouterData::try_from(( - &self.get_currency_unit(), + let amount = connector_utils::convert_amount( + self.amount_converter, + req.request.minor_amount_to_capture, req.request.currency, - req.request.amount_to_capture, - req, - ))?; + )?; + let connector_router_data = klarna::KlarnaRouterData::from((amount, req)); let connector_req = klarna::KlarnaCaptureRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -434,7 +449,24 @@ impl let endpoint = build_region_specific_endpoint(self.base_url(connectors), &req.connector_meta_data)?; - Ok(format!("{endpoint}ordermanagement/v1/orders/{order_id}")) + let payment_experience = req.request.payment_experience; + + match payment_experience { + Some(common_enums::PaymentExperience::InvokeSdkClient) => { + Ok(format!("{endpoint}ordermanagement/v1/orders/{order_id}")) + } + Some(common_enums::PaymentExperience::RedirectToUrl) => { + Ok(format!("{endpoint}checkout/v3/orders/{order_id}")) + } + None => Err(error_stack::report!(errors::ConnectorError::NotSupported { + message: "payment_experience not supported".to_string(), + connector: "klarna", + })), + _ => Err(error_stack::report!(errors::ConnectorError::NotSupported { + message: "payment_experience not supported".to_string(), + connector: "klarna", + })), + } } fn build_request( @@ -536,7 +568,124 @@ impl | common_enums::PaymentExperience::InvokeSdkClient | common_enums::PaymentExperience::LinkWallet | common_enums::PaymentExperience::OneClick - | common_enums::PaymentExperience::RedirectToUrl, + | common_enums::PaymentExperience::RedirectToUrl + | common_enums::PaymentExperience::CollectOtp, + common_enums::PaymentMethodType::Ach + | common_enums::PaymentMethodType::Affirm + | common_enums::PaymentMethodType::AfterpayClearpay + | common_enums::PaymentMethodType::Alfamart + | common_enums::PaymentMethodType::AliPay + | common_enums::PaymentMethodType::AliPayHk + | common_enums::PaymentMethodType::Alma + | common_enums::PaymentMethodType::ApplePay + | common_enums::PaymentMethodType::Atome + | common_enums::PaymentMethodType::Bacs + | common_enums::PaymentMethodType::BancontactCard + | common_enums::PaymentMethodType::Becs + | common_enums::PaymentMethodType::Benefit + | common_enums::PaymentMethodType::Bizum + | common_enums::PaymentMethodType::Blik + | common_enums::PaymentMethodType::Boleto + | common_enums::PaymentMethodType::BcaBankTransfer + | common_enums::PaymentMethodType::BniVa + | common_enums::PaymentMethodType::BriVa + | common_enums::PaymentMethodType::CardRedirect + | common_enums::PaymentMethodType::CimbVa + | common_enums::PaymentMethodType::ClassicReward + | common_enums::PaymentMethodType::Credit + | common_enums::PaymentMethodType::CryptoCurrency + | common_enums::PaymentMethodType::Cashapp + | common_enums::PaymentMethodType::Dana + | common_enums::PaymentMethodType::DanamonVa + | common_enums::PaymentMethodType::Debit + | common_enums::PaymentMethodType::DirectCarrierBilling + | common_enums::PaymentMethodType::Efecty + | common_enums::PaymentMethodType::Eps + | common_enums::PaymentMethodType::Evoucher + | common_enums::PaymentMethodType::Giropay + | common_enums::PaymentMethodType::Givex + | common_enums::PaymentMethodType::GooglePay + | common_enums::PaymentMethodType::GoPay + | common_enums::PaymentMethodType::Gcash + | common_enums::PaymentMethodType::Ideal + | common_enums::PaymentMethodType::Interac + | common_enums::PaymentMethodType::Indomaret + | common_enums::PaymentMethodType::Klarna + | common_enums::PaymentMethodType::KakaoPay + | common_enums::PaymentMethodType::MandiriVa + | common_enums::PaymentMethodType::Knet + | common_enums::PaymentMethodType::MbWay + | common_enums::PaymentMethodType::MobilePay + | common_enums::PaymentMethodType::Momo + | common_enums::PaymentMethodType::MomoAtm + | common_enums::PaymentMethodType::Multibanco + | common_enums::PaymentMethodType::LocalBankRedirect + | common_enums::PaymentMethodType::OnlineBankingThailand + | common_enums::PaymentMethodType::OnlineBankingCzechRepublic + | common_enums::PaymentMethodType::OnlineBankingFinland + | common_enums::PaymentMethodType::OnlineBankingFpx + | common_enums::PaymentMethodType::OnlineBankingPoland + | common_enums::PaymentMethodType::OnlineBankingSlovakia + | common_enums::PaymentMethodType::Oxxo + | common_enums::PaymentMethodType::PagoEfectivo + | common_enums::PaymentMethodType::PermataBankTransfer + | common_enums::PaymentMethodType::OpenBankingUk + | common_enums::PaymentMethodType::PayBright + | common_enums::PaymentMethodType::Paypal + | common_enums::PaymentMethodType::Paze + | common_enums::PaymentMethodType::Pix + | common_enums::PaymentMethodType::PaySafeCard + | common_enums::PaymentMethodType::Przelewy24 + | common_enums::PaymentMethodType::Pse + | common_enums::PaymentMethodType::RedCompra + | common_enums::PaymentMethodType::RedPagos + | common_enums::PaymentMethodType::SamsungPay + | common_enums::PaymentMethodType::Sepa + | common_enums::PaymentMethodType::Sofort + | common_enums::PaymentMethodType::Swish + | common_enums::PaymentMethodType::TouchNGo + | common_enums::PaymentMethodType::Trustly + | common_enums::PaymentMethodType::Twint + | common_enums::PaymentMethodType::UpiCollect + | common_enums::PaymentMethodType::UpiIntent + | common_enums::PaymentMethodType::Venmo + | common_enums::PaymentMethodType::Vipps + | common_enums::PaymentMethodType::Walley + | common_enums::PaymentMethodType::WeChatPay + | common_enums::PaymentMethodType::SevenEleven + | common_enums::PaymentMethodType::Lawson + | common_enums::PaymentMethodType::LocalBankTransfer + | common_enums::PaymentMethodType::MiniStop + | common_enums::PaymentMethodType::FamilyMart + | common_enums::PaymentMethodType::Seicomart + | common_enums::PaymentMethodType::PayEasy + | common_enums::PaymentMethodType::Mifinity + | common_enums::PaymentMethodType::Fps + | common_enums::PaymentMethodType::DuitNow + | common_enums::PaymentMethodType::PromptPay + | common_enums::PaymentMethodType::VietQr + | common_enums::PaymentMethodType::OpenBankingPIS, + ) => Err(error_stack::report!(errors::ConnectorError::NotSupported { + message: payment_method_type.to_string(), + connector: "klarna", + })), + } + } + domain::PaymentMethodData::PayLater(domain::PayLaterData::KlarnaCheckout {}) => { + match (payment_experience, payment_method_type) { + ( + common_enums::PaymentExperience::RedirectToUrl, + common_enums::PaymentMethodType::Klarna, + ) => Ok(format!("{endpoint}checkout/v3/orders",)), + ( + common_enums::PaymentExperience::DisplayQrCode + | common_enums::PaymentExperience::DisplayWaitScreen + | common_enums::PaymentExperience::InvokePaymentApp + | common_enums::PaymentExperience::InvokeSdkClient + | common_enums::PaymentExperience::LinkWallet + | common_enums::PaymentExperience::OneClick + | common_enums::PaymentExperience::RedirectToUrl + | common_enums::PaymentExperience::CollectOtp, common_enums::PaymentMethodType::Ach | common_enums::PaymentMethodType::Affirm | common_enums::PaymentMethodType::AfterpayClearpay @@ -565,6 +714,7 @@ impl | common_enums::PaymentMethodType::Dana | common_enums::PaymentMethodType::DanamonVa | common_enums::PaymentMethodType::Debit + | common_enums::PaymentMethodType::DirectCarrierBilling | common_enums::PaymentMethodType::Efecty | common_enums::PaymentMethodType::Eps | common_enums::PaymentMethodType::Evoucher @@ -598,6 +748,7 @@ impl | common_enums::PaymentMethodType::OpenBankingUk | common_enums::PaymentMethodType::PayBright | common_enums::PaymentMethodType::Paypal + | common_enums::PaymentMethodType::Paze | common_enums::PaymentMethodType::Pix | common_enums::PaymentMethodType::PaySafeCard | common_enums::PaymentMethodType::Przelewy24 @@ -648,12 +799,14 @@ impl | domain::PaymentMethodData::MandatePayment | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(report!(errors::ConnectorError::NotImplemented( connector_utils::get_unimplemented_payment_method_error_message( req.connector.as_str(), @@ -668,12 +821,12 @@ impl req: &types::PaymentsAuthorizeRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_router_data = klarna::KlarnaRouterData::try_from(( - &self.get_currency_unit(), + let amount = connector_utils::convert_amount( + self.amount_converter, + req.request.minor_amount, req.request.currency, - req.request.amount, - req, - ))?; + )?; + let connector_router_data = klarna::KlarnaRouterData::from((amount, req)); let connector_req = klarna::KlarnaPaymentsRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -707,7 +860,7 @@ impl event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: klarna::KlarnaPaymentsResponse = res + let response: klarna::KlarnaAuthResponse = res .response .parse_struct("KlarnaPaymentsResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; @@ -845,12 +998,12 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_router_data = klarna::KlarnaRouterData::try_from(( - &self.get_currency_unit(), + let amount = connector_utils::convert_amount( + self.amount_converter, + req.request.minor_refund_amount, req.request.currency, - req.request.refund_amount, - req, - ))?; + )?; + let connector_router_data = klarna::KlarnaRouterData::from((amount, req)); let connector_req = klarna::KlarnaRefundRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -1006,3 +1159,5 @@ impl api::IncomingWebhook for Klarna { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for Klarna {} diff --git a/crates/router/src/connector/klarna/transformers.rs b/crates/router/src/connector/klarna/transformers.rs index e6f0bab9c2c8..3cefa4ec6c9b 100644 --- a/crates/router/src/connector/klarna/transformers.rs +++ b/crates/router/src/connector/klarna/transformers.rs @@ -1,7 +1,9 @@ use api_models::payments; -use common_utils::pii; +use common_utils::{pii, types::MinorUnit}; use error_stack::{report, ResultExt}; -use hyperswitch_domain_models::router_data::KlarnaSdkResponse; +use hyperswitch_domain_models::{ + router_data::KlarnaSdkResponse, router_response_types::RedirectForm, +}; use masking::{ExposeInterface, Secret}; use serde::{Deserialize, Serialize}; @@ -10,30 +12,21 @@ use crate::{ self, AddressData, AddressDetailsData, PaymentsAuthorizeRequestData, RouterData, }, core::errors, - types::{self, api, storage::enums, transformers::ForeignFrom}, + types::{self, api, domain, storage::enums, transformers::ForeignFrom}, }; #[derive(Debug, Serialize)] pub struct KlarnaRouterData { - amount: i64, + amount: MinorUnit, router_data: T, } -impl TryFrom<(&api::CurrencyUnit, enums::Currency, i64, T)> for KlarnaRouterData { - type Error = error_stack::Report; - - fn try_from( - (_currency_unit, _currency, amount, router_data): ( - &api::CurrencyUnit, - enums::Currency, - i64, - T, - ), - ) -> Result { - Ok(Self { +impl From<(MinorUnit, T)> for KlarnaRouterData { + fn from((amount, router_data): (MinorUnit, T)) -> Self { + Self { amount, router_data, - }) + } } } @@ -70,25 +63,62 @@ impl TryFrom<&Option> for KlarnaConnectorMetadataObject { } } -#[derive(Default, Debug, Serialize)] +#[derive(Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum PaymentMethodSpecifics { + KlarnaCheckout(KlarnaCheckoutRequestData), + KlarnaSdk, +} + +#[derive(Default, Debug, Serialize, Deserialize)] +pub struct MerchantURLs { + terms: String, + checkout: String, + confirmation: String, + push: String, +} + +#[derive(Default, Debug, Serialize, Deserialize)] +pub struct KlarnaCheckoutRequestData { + merchant_urls: MerchantURLs, + options: CheckoutOptions, +} + +#[derive(Default, Debug, Deserialize, Serialize)] pub struct KlarnaPaymentsRequest { - auto_capture: bool, order_lines: Vec, - order_amount: i64, + order_amount: MinorUnit, purchase_country: enums::CountryAlpha2, purchase_currency: enums::Currency, merchant_reference1: Option, merchant_reference2: Option, shipping_address: Option, + auto_capture: Option, + order_tax_amount: Option, + #[serde(flatten)] + payment_method_specifics: Option, } #[derive(Debug, Deserialize, Serialize)] -pub struct KlarnaPaymentsResponse { +#[serde(untagged)] +pub enum KlarnaAuthResponse { + KlarnaPaymentsAuthResponse(PaymentsResponse), + KlarnaCheckoutAuthResponse(CheckoutResponse), +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct PaymentsResponse { order_id: String, fraud_status: KlarnaFraudStatus, authorized_payment_method: Option, } +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct CheckoutResponse { + order_id: String, + status: KlarnaCheckoutStatus, + html_snippet: String, +} #[derive(Debug, Clone, Deserialize, Serialize)] pub struct AuthorizedPaymentMethod { #[serde(rename = "type")] @@ -110,12 +140,12 @@ pub struct KlarnaSessionRequest { intent: KlarnaSessionIntent, purchase_country: enums::CountryAlpha2, purchase_currency: enums::Currency, - order_amount: i64, + order_amount: MinorUnit, order_lines: Vec, shipping_address: Option, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct KlarnaShippingAddress { city: String, country: enums::CountryAlpha2, @@ -129,6 +159,10 @@ pub struct KlarnaShippingAddress { street_address2: Option>, } +#[derive(Default, Debug, Serialize, Deserialize)] +pub struct CheckoutOptions { + auto_capture: bool, +} #[derive(Deserialize, Serialize, Debug)] pub struct KlarnaSessionResponse { pub client_token: Secret, @@ -157,7 +191,9 @@ impl TryFrom<&KlarnaRouterData<&types::PaymentsSessionRouterData>> for KlarnaSes name: data.product_name.clone(), quantity: data.quantity, unit_price: data.amount, - total_amount: i64::from(data.quantity) * (data.amount), + total_amount: data.amount * data.quantity, + total_tax_amount: None, + tax_rate: None, }) .collect(), shipping_address: get_address_info(item.router_data.get_optional_shipping()) @@ -199,35 +235,106 @@ impl TryFrom<&KlarnaRouterData<&types::PaymentsAuthorizeRouterData>> for KlarnaP item: &KlarnaRouterData<&types::PaymentsAuthorizeRouterData>, ) -> Result { let request = &item.router_data.request; - match request.order_details.clone() { - Some(order_details) => Ok(Self { - purchase_country: item.router_data.get_billing_country()?, - purchase_currency: request.currency, - order_amount: item.amount, - order_lines: order_details - .iter() - .map(|data| OrderLines { - name: data.product_name.clone(), - quantity: data.quantity, - unit_price: data.amount, - total_amount: i64::from(data.quantity) * (data.amount), - }) - .collect(), - merchant_reference1: Some(item.router_data.connector_request_reference_id.clone()), - merchant_reference2: item.router_data.request.merchant_order_reference_id.clone(), - auto_capture: request.is_auto_capture()?, - shipping_address: get_address_info(item.router_data.get_optional_shipping()) - .transpose()?, - }), - None => Err(report!(errors::ConnectorError::MissingRequiredField { - field_name: "order_details" - })), + let payment_method_data = request.payment_method_data.clone(); + let return_url = item.router_data.request.get_router_return_url()?; + let webhook_url = item.router_data.request.get_webhook_url()?; + match payment_method_data { + domain::PaymentMethodData::PayLater(domain::PayLaterData::KlarnaSdk { .. }) => { + match request.order_details.clone() { + Some(order_details) => Ok(Self { + purchase_country: item.router_data.get_billing_country()?, + purchase_currency: request.currency, + order_amount: item.amount, + order_lines: order_details + .iter() + .map(|data| OrderLines { + name: data.product_name.clone(), + quantity: data.quantity, + unit_price: data.amount, + total_amount: data.amount * data.quantity, + total_tax_amount: None, + tax_rate: None, + }) + .collect(), + merchant_reference1: Some( + item.router_data.connector_request_reference_id.clone(), + ), + merchant_reference2: item + .router_data + .request + .merchant_order_reference_id + .clone(), + auto_capture: Some(request.is_auto_capture()?), + shipping_address: get_address_info( + item.router_data.get_optional_shipping(), + ) + .transpose()?, + order_tax_amount: None, + payment_method_specifics: None, + }), + None => Err(report!(errors::ConnectorError::MissingRequiredField { + field_name: "order_details" + })), + } + } + domain::PaymentMethodData::PayLater(domain::PayLaterData::KlarnaCheckout {}) => { + match request.order_details.clone() { + Some(order_details) => Ok(Self { + purchase_country: item.router_data.get_billing_country()?, + purchase_currency: request.currency, + order_amount: item.amount + - request.order_tax_amount.unwrap_or(MinorUnit::zero()), + order_tax_amount: request.order_tax_amount, + order_lines: order_details + .iter() + .map(|data| OrderLines { + name: data.product_name.clone(), + quantity: data.quantity, + unit_price: data.amount, + total_amount: data.amount * data.quantity, + total_tax_amount: data.total_tax_amount, + tax_rate: data.tax_rate, + }) + .collect(), + payment_method_specifics: Some(PaymentMethodSpecifics::KlarnaCheckout( + KlarnaCheckoutRequestData { + merchant_urls: MerchantURLs { + terms: return_url.clone(), + checkout: return_url.clone(), + confirmation: return_url, + push: webhook_url, + }, + options: CheckoutOptions { + auto_capture: request.is_auto_capture()?, + }, + }, + )), + shipping_address: get_address_info( + item.router_data.get_optional_shipping(), + ) + .transpose()?, + merchant_reference1: Some( + item.router_data.connector_request_reference_id.clone(), + ), + merchant_reference2: item + .router_data + .request + .merchant_order_reference_id + .clone(), + auto_capture: None, + }), + None => Err(report!(errors::ConnectorError::MissingRequiredField { + field_name: "order_details" + })), + } + } + _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), } } } fn get_address_info( - address: Option<&payments::Address>, + address: Option<&hyperswitch_domain_models::address::Address>, ) -> Option>> { address.and_then(|add| { add.address.as_ref().map( @@ -249,53 +356,82 @@ fn get_address_info( }) } -impl TryFrom> +impl TryFrom> for types::PaymentsAuthorizeRouterData { type Error = error_stack::Report; + fn try_from( - item: types::PaymentsResponseRouterData, + item: types::PaymentsResponseRouterData, ) -> Result { - let connector_response = types::ConnectorResponseData::with_additional_payment_method_data( - match item.response.authorized_payment_method { - Some(authorized_payment_method) => { - types::AdditionalPaymentMethodConnectorResponse::from(authorized_payment_method) - } - None => { - types::AdditionalPaymentMethodConnectorResponse::PayLater { klarna_sdk: None } - } - }, - ); - - Ok(Self { - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - item.response.order_id.clone(), - ), - redirection_data: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: Some(item.response.order_id.clone()), - incremental_authorization_allowed: None, - charge_id: None, + match item.response { + KlarnaAuthResponse::KlarnaPaymentsAuthResponse(ref response) => { + let connector_response = + response + .authorized_payment_method + .as_ref() + .map(|authorized_payment_method| { + types::ConnectorResponseData::with_additional_payment_method_data( + types::AdditionalPaymentMethodConnectorResponse::from( + authorized_payment_method.clone(), + ), + ) + }); + + Ok(Self { + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + response.order_id.clone(), + ), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(response.order_id.clone()), + incremental_authorization_allowed: None, + charge_id: None, + }), + status: enums::AttemptStatus::foreign_from(( + response.fraud_status.clone(), + item.data.request.is_auto_capture()?, + )), + connector_response, + ..item.data + }) + } + KlarnaAuthResponse::KlarnaCheckoutAuthResponse(ref response) => Ok(Self { + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + response.order_id.clone(), + ), + redirection_data: Box::new(Some(RedirectForm::Html { + html_data: response.html_snippet.clone(), + })), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(response.order_id.clone()), + incremental_authorization_allowed: None, + charge_id: None, + }), + status: enums::AttemptStatus::foreign_from(( + response.status.clone(), + item.data.request.is_auto_capture()?, + )), + connector_response: None, + ..item.data }), - status: enums::AttemptStatus::foreign_from(( - item.response.fraud_status, - item.data.request.is_auto_capture()?, - )), - connector_response: Some(connector_response), - ..item.data - }) + } } } - -#[derive(Debug, Serialize)] +#[derive(Default, Debug, Serialize, Deserialize)] pub struct OrderLines { name: String, quantity: u16, - unit_price: i64, - total_amount: i64, + unit_price: MinorUnit, + total_amount: MinorUnit, + total_tax_amount: Option, + tax_rate: Option, } #[derive(Debug, Serialize)] @@ -334,6 +470,13 @@ pub enum KlarnaFraudStatus { Rejected, } +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum KlarnaCheckoutStatus { + CheckoutComplete, + CheckoutIncomplete, +} + impl ForeignFrom<(KlarnaFraudStatus, bool)> for enums::AttemptStatus { fn foreign_from((klarna_status, is_auto_capture): (KlarnaFraudStatus, bool)) -> Self { match klarna_status { @@ -350,13 +493,42 @@ impl ForeignFrom<(KlarnaFraudStatus, bool)> for enums::AttemptStatus { } } +impl ForeignFrom<(KlarnaCheckoutStatus, bool)> for enums::AttemptStatus { + fn foreign_from((klarna_status, is_auto_capture): (KlarnaCheckoutStatus, bool)) -> Self { + match klarna_status { + KlarnaCheckoutStatus::CheckoutIncomplete => { + if is_auto_capture { + Self::AuthenticationPending + } else { + Self::Authorized + } + } + KlarnaCheckoutStatus::CheckoutComplete => Self::Charged, + } + } +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum KlarnaPsyncResponse { + KlarnaSDKPsyncResponse(KlarnaSDKSyncResponse), + KlarnaCheckoutPSyncResponse(KlarnaCheckoutSyncResponse), +} + #[derive(Debug, Serialize, Deserialize)] -pub struct KlarnaPsyncResponse { +pub struct KlarnaSDKSyncResponse { pub order_id: String, pub status: KlarnaPaymentStatus, pub klarna_reference: Option, } +#[derive(Debug, Serialize, Deserialize)] +pub struct KlarnaCheckoutSyncResponse { + pub order_id: String, + pub status: KlarnaCheckoutStatus, + pub options: CheckoutOptions, +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum KlarnaPaymentStatus { @@ -388,31 +560,51 @@ impl fn try_from( item: types::ResponseRouterData, ) -> Result { - Ok(Self { - status: enums::AttemptStatus::from(item.response.status), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - item.response.order_id.clone(), - ), - redirection_data: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: item - .response - .klarna_reference - .or(Some(item.response.order_id)), - incremental_authorization_allowed: None, - charge_id: None, + match item.response { + KlarnaPsyncResponse::KlarnaSDKPsyncResponse(response) => Ok(Self { + status: enums::AttemptStatus::from(response.status), + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + response.order_id.clone(), + ), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: response + .klarna_reference + .or(Some(response.order_id.clone())), + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data }), - ..item.data - }) + KlarnaPsyncResponse::KlarnaCheckoutPSyncResponse(response) => Ok(Self { + status: enums::AttemptStatus::foreign_from(( + response.status.clone(), + response.options.auto_capture, + )), + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + response.order_id.clone(), + ), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(response.order_id.clone()), + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }), + } } } #[derive(Debug, Serialize)] pub struct KlarnaCaptureRequest { - captured_amount: i64, + captured_amount: MinorUnit, reference: Option, } @@ -474,8 +666,8 @@ impl Ok(Self { response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(resource_id), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: Some(connector_meta), network_txn_id: None, connector_response_reference_id: None, @@ -490,7 +682,7 @@ impl #[derive(Default, Debug, Serialize)] pub struct KlarnaRefundRequest { - refunded_amount: i64, + refunded_amount: MinorUnit, reference: Option, } diff --git a/crates/router/src/connector/mifinity.rs b/crates/router/src/connector/mifinity.rs index d8ac2d101297..dcf95a576e3f 100644 --- a/crates/router/src/connector/mifinity.rs +++ b/crates/router/src/connector/mifinity.rs @@ -16,7 +16,7 @@ use crate::{ services::{ self, request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, + ConnectorIntegration, ConnectorSpecifications, ConnectorValidation, }, types::{ self, @@ -501,3 +501,5 @@ impl api::IncomingWebhook for Mifinity { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for Mifinity {} diff --git a/crates/router/src/connector/mifinity/transformers.rs b/crates/router/src/connector/mifinity/transformers.rs index 0af3826c877f..1e2c420c767b 100644 --- a/crates/router/src/connector/mifinity/transformers.rs +++ b/crates/router/src/connector/mifinity/transformers.rs @@ -169,6 +169,7 @@ impl TryFrom<&MifinityRouterData<&types::PaymentsAuthorizeRouterData>> for Mifin | domain::WalletData::MobilePayRedirect(_) | domain::WalletData::PaypalRedirect(_) | domain::WalletData::PaypalSdk(_) + | domain::WalletData::Paze(_) | domain::WalletData::SamsungPay(_) | domain::WalletData::TwintRedirect {} | domain::WalletData::VippsRedirect {} @@ -191,12 +192,14 @@ impl TryFrom<&MifinityRouterData<&types::PaymentsAuthorizeRouterData>> for Mifin | domain::PaymentMethodData::MandatePayment | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Mifinity"), ) @@ -257,10 +260,10 @@ impl status: enums::AttemptStatus::AuthenticationPending, response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(trace_id.clone()), - redirection_data: Some(services::RedirectForm::Mifinity { + redirection_data: Box::new(Some(services::RedirectForm::Mifinity { initialization_token, - }), - mandate_reference: None, + })), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(trace_id), @@ -274,8 +277,8 @@ impl status: enums::AttemptStatus::AuthenticationPending, response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::NoResponseId, - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -343,8 +346,8 @@ impl resource_id: types::ResponseId::ConnectorTransactionId( transaction_reference, ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -358,8 +361,8 @@ impl status: enums::AttemptStatus::from(status), response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::NoResponseId, - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -374,8 +377,8 @@ impl status: item.data.status, response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::NoResponseId, - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, diff --git a/crates/router/src/connector/netcetera.rs b/crates/router/src/connector/netcetera.rs index edf56365f4ef..459ac93e0680 100644 --- a/crates/router/src/connector/netcetera.rs +++ b/crates/router/src/connector/netcetera.rs @@ -13,7 +13,7 @@ use crate::{ core::errors::{self, CustomResult}, events::connector_api_logs::ConnectorEvent, headers, - services::{self, request, ConnectorIntegration, ConnectorValidation}, + services::{self, request, ConnectorIntegration, ConnectorSpecifications, ConnectorValidation}, types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, @@ -462,3 +462,5 @@ impl > for Netcetera { } + +impl ConnectorSpecifications for Netcetera {} diff --git a/crates/router/src/connector/netcetera/netcetera_types.rs b/crates/router/src/connector/netcetera/netcetera_types.rs index 2718fb3b238a..ac6aad8c1366 100644 --- a/crates/router/src/connector/netcetera/netcetera_types.rs +++ b/crates/router/src/connector/netcetera/netcetera_types.rs @@ -208,7 +208,7 @@ pub struct ThreeDSRequestorAuthenticationInformation { /// card to a wallet. /// /// This field is optional. The accepted values are: -/// +/// /// - 01 -> No preference /// - 02 -> No challenge requested /// - 03 -> Challenge requested: 3DS Requestor Preference @@ -686,15 +686,15 @@ pub struct Cardholder { impl TryFrom<( - api_models::payments::Address, - Option, + hyperswitch_domain_models::address::Address, + Option, )> for Cardholder { type Error = error_stack::Report; fn try_from( (billing_address, shipping_address): ( - api_models::payments::Address, - Option, + hyperswitch_domain_models::address::Address, + Option, ), ) -> Result { Ok(Self { @@ -801,9 +801,11 @@ pub struct PhoneNumber { subscriber: Option>, } -impl TryFrom for PhoneNumber { +impl TryFrom for PhoneNumber { type Error = error_stack::Report; - fn try_from(value: api_models::payments::PhoneDetails) -> Result { + fn try_from( + value: hyperswitch_domain_models::address::PhoneDetails, + ) -> Result { Ok(Self { country_code: Some(value.extract_country_code()?), subscriber: value.number, @@ -1391,6 +1393,32 @@ impl From for Browser { } } +impl From> for ThreeDSRequestor { + fn from(value: Option) -> Self { + // if sca exemption is provided, we need to set the challenge indicator to NoChallengeRequestedTransactionalRiskAnalysis + let three_ds_requestor_challenge_ind = + if let Some(common_enums::ScaExemptionType::TransactionRiskAnalysis) = value { + Some(SingleOrListElement::Single( + ThreeDSRequestorChallengeIndicator::NoChallengeRequestedTransactionalRiskAnalysis, + )) + } else { + None + }; + + Self { + three_ds_requestor_authentication_ind: ThreeDSRequestorAuthenticationIndicator::Payment, + three_ds_requestor_authentication_info: None, + three_ds_requestor_challenge_ind, + three_ds_requestor_prior_authentication_info: None, + three_ds_requestor_dec_req_ind: None, + three_ds_requestor_dec_max_time: None, + app_ip: None, + three_ds_requestor_spc_support: None, + spc_incomp_ind: None, + } + } +} + #[derive(Serialize, Deserialize, Debug, Clone)] pub enum ChallengeWindowSizeEnum { #[serde(rename = "01")] diff --git a/crates/router/src/connector/netcetera/transformers.rs b/crates/router/src/connector/netcetera/transformers.rs index adcfb17f14b3..ab228d95a9e5 100644 --- a/crates/router/src/connector/netcetera/transformers.rs +++ b/crates/router/src/connector/netcetera/transformers.rs @@ -456,18 +456,8 @@ impl TryFrom<&NetceteraRouterData<&types::authentication::ConnectorAuthenticatio let now = common_utils::date_time::now(); let request = item.router_data.request.clone(); let pre_authn_data = request.pre_authentication_data.clone(); - let three_ds_requestor = netcetera_types::ThreeDSRequestor { - three_ds_requestor_authentication_ind: - netcetera_types::ThreeDSRequestorAuthenticationIndicator::Payment, - three_ds_requestor_authentication_info: None, - three_ds_requestor_challenge_ind: None, - three_ds_requestor_prior_authentication_info: None, - three_ds_requestor_dec_req_ind: None, - three_ds_requestor_dec_max_time: None, - app_ip: None, - three_ds_requestor_spc_support: None, - spc_incomp_ind: None, - }; + let three_ds_requestor = + netcetera_types::ThreeDSRequestor::from(item.router_data.psd2_sca_exemption_type); let card = utils::get_card_details(request.payment_method_data, "netcetera")?; let cardholder_account = netcetera_types::CardholderAccount { acct_type: None, diff --git a/crates/router/src/connector/nmi.rs b/crates/router/src/connector/nmi.rs index 76665c2700c2..73aaec55e574 100644 --- a/crates/router/src/connector/nmi.rs +++ b/crates/router/src/connector/nmi.rs @@ -19,7 +19,7 @@ use crate::{ payments, }, events::connector_api_logs::ConnectorEvent, - services::{self, request, ConnectorIntegration, ConnectorValidation}, + services::{self, request, ConnectorIntegration, ConnectorSpecifications, ConnectorValidation}, types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, @@ -108,14 +108,17 @@ impl ConnectorCommon for Nmi { } impl ConnectorValidation for Nmi { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( connector_utils::construct_not_supported_error_report(capture_method, self.id()), ), @@ -1031,3 +1034,5 @@ impl services::ConnectorRedirectResponse for Nmi { } } } + +impl ConnectorSpecifications for Nmi {} diff --git a/crates/router/src/connector/nmi/transformers.rs b/crates/router/src/connector/nmi/transformers.rs index 069a51ae52e7..4c33f0200172 100644 --- a/crates/router/src/connector/nmi/transformers.rs +++ b/crates/router/src/connector/nmi/transformers.rs @@ -185,7 +185,7 @@ impl Response::Approved => ( Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::NoResponseId, - redirection_data: Some(services::RedirectForm::Nmi { + redirection_data: Box::new(Some(services::RedirectForm::Nmi { amount: utils::to_currency_base_unit_asf64( amount_data, currency_data.to_owned(), @@ -206,8 +206,8 @@ impl }, )?, order_id: item.data.connector_request_reference_id.clone(), - }), - mandate_reference: None, + })), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(item.response.transactionid), @@ -360,8 +360,8 @@ impl resource_id: types::ResponseId::ConnectorTransactionId( item.response.transactionid, ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(item.response.orderid), @@ -558,6 +558,7 @@ impl | domain::WalletData::MobilePayRedirect(_) | domain::WalletData::PaypalRedirect(_) | domain::WalletData::PaypalSdk(_) + | domain::WalletData::Paze(_) | domain::WalletData::SamsungPay(_) | domain::WalletData::TwintRedirect {} | domain::WalletData::VippsRedirect {} @@ -581,12 +582,14 @@ impl | domain::PaymentMethodData::MandatePayment | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("nmi"), ) @@ -741,8 +744,8 @@ impl resource_id: types::ResponseId::ConnectorTransactionId( item.response.transactionid.to_owned(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(item.response.orderid), @@ -836,8 +839,8 @@ impl resource_id: types::ResponseId::ConnectorTransactionId( item.response.transactionid.clone(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(item.response.orderid), @@ -893,8 +896,8 @@ impl TryFrom> resource_id: types::ResponseId::ConnectorTransactionId( item.response.transactionid.clone(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(item.response.orderid), @@ -944,8 +947,8 @@ impl resource_id: types::ResponseId::ConnectorTransactionId( item.response.transactionid.clone(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(item.response.orderid), @@ -995,8 +998,8 @@ impl TryFrom, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( connector_utils::construct_not_implemented_error_report(capture_method, self.id()), ), @@ -943,3 +946,5 @@ impl api::IncomingWebhook for Noon { Ok(Box::new(noon::NoonPaymentsResponse::from(resource))) } } + +impl ConnectorSpecifications for Noon {} diff --git a/crates/router/src/connector/noon/transformers.rs b/crates/router/src/connector/noon/transformers.rs index 5d9871742730..3e75130f575d 100644 --- a/crates/router/src/connector/noon/transformers.rs +++ b/crates/router/src/connector/noon/transformers.rs @@ -326,6 +326,7 @@ impl TryFrom<&NoonRouterData<&types::PaymentsAuthorizeRouterData>> for NoonPayme | domain::WalletData::MbWayRedirect(_) | domain::WalletData::MobilePayRedirect(_) | domain::WalletData::PaypalSdk(_) + | domain::WalletData::Paze(_) | domain::WalletData::SamsungPay(_) | domain::WalletData::TwintRedirect {} | domain::WalletData::VippsRedirect {} @@ -349,12 +350,14 @@ impl TryFrom<&NoonRouterData<&types::PaymentsAuthorizeRouterData>> for NoonPayme | domain::PaymentMethodData::MandatePayment {} | domain::PaymentMethodData::Reward {} | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( conn_utils::get_unimplemented_payment_method_error_message("Noon"), )) @@ -575,6 +578,7 @@ impl connector_mandate_id: Some(subscription_data.identifier.expose()), payment_method_id: None, mandate_metadata: None, + connector_mandate_request_reference_id: None, }); Ok(Self { status, @@ -594,8 +598,8 @@ impl resource_id: types::ResponseId::ConnectorTransactionId( order.id.to_string(), ), - redirection_data, - mandate_reference, + redirection_data: Box::new(redirection_data), + mandate_reference: Box::new(mandate_reference), connector_metadata: None, network_txn_id: None, connector_response_reference_id, diff --git a/crates/router/src/connector/nuvei.rs b/crates/router/src/connector/nuvei.rs index 8108413b4eea..c72bee24fa28 100644 --- a/crates/router/src/connector/nuvei.rs +++ b/crates/router/src/connector/nuvei.rs @@ -21,7 +21,7 @@ use crate::{ }, events::connector_api_logs::ConnectorEvent, headers, - services::{self, request, ConnectorIntegration, ConnectorValidation}, + services::{self, request, ConnectorIntegration, ConnectorSpecifications, ConnectorValidation}, types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, @@ -73,14 +73,17 @@ impl ConnectorCommon for Nuvei { } impl ConnectorValidation for Nuvei { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( utils::construct_not_supported_error_report(capture_method, self.id()), ), @@ -978,3 +981,5 @@ impl services::ConnectorRedirectResponse for Nuvei { } } } + +impl ConnectorSpecifications for Nuvei {} diff --git a/crates/router/src/connector/nuvei/transformers.rs b/crates/router/src/connector/nuvei/transformers.rs index f280d3a42a2a..c98a06477787 100644 --- a/crates/router/src/connector/nuvei/transformers.rs +++ b/crates/router/src/connector/nuvei/transformers.rs @@ -71,7 +71,7 @@ impl NuveiAuthorizePreprocessingCommon for types::PaymentsAuthorizeData { fn get_return_url_required( &self, ) -> Result> { - self.get_return_url() + self.get_router_return_url() } fn get_capture_method(&self) -> Option { @@ -122,7 +122,7 @@ impl NuveiAuthorizePreprocessingCommon for types::PaymentsPreProcessingData { fn get_return_url_required( &self, ) -> Result> { - self.get_return_url() + self.get_router_return_url() } fn get_capture_method(&self) -> Option { @@ -914,6 +914,7 @@ where | domain::WalletData::MbWayRedirect(_) | domain::WalletData::MobilePayRedirect(_) | domain::WalletData::PaypalSdk(_) + | domain::WalletData::Paze(_) | domain::WalletData::SamsungPay(_) | domain::WalletData::TwintRedirect {} | domain::WalletData::VippsRedirect {} @@ -976,6 +977,7 @@ where get_pay_later_info(AlternativePaymentMethodType::AfterPay, item) } domain::PayLaterData::KlarnaSdk { .. } + | domain::PayLaterData::KlarnaCheckout {} | domain::PayLaterData::AffirmRedirect {} | domain::PayLaterData::PayBrightRedirect {} | domain::PayLaterData::WalleyRedirect {} @@ -992,13 +994,15 @@ where | domain::PaymentMethodData::Crypto(_) | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::CardRedirect(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("nuvei"), ) @@ -1126,7 +1130,7 @@ where browser_details, v2_additional_params: additional_params, notification_url: item.request.get_complete_authorize_url().clone(), - merchant_url: item.return_url.clone(), + merchant_url: Some(item.request.get_return_url_required()?), platform_type: Some(PlatformType::Browser), method_completion_ind: Some(MethodCompletion::Unavailable), ..Default::default() @@ -1198,10 +1202,12 @@ impl TryFrom<(&types::PaymentsCompleteAuthorizeRouterData, Secret)> | Some(domain::PaymentMethodData::CardRedirect(..)) | Some(domain::PaymentMethodData::Reward) | Some(domain::PaymentMethodData::RealTimePayment(..)) + | Some(domain::PaymentMethodData::MobilePayment(..)) | Some(domain::PaymentMethodData::Upi(..)) | Some(domain::PaymentMethodData::OpenBanking(_)) | Some(domain::PaymentMethodData::CardToken(..)) | Some(domain::PaymentMethodData::NetworkToken(..)) + | Some(domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_)) | None => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("nuvei"), )), @@ -1595,15 +1601,18 @@ where .map_or(response.order_id.clone(), Some) // For paypal there will be no transaction_id, only order_id will be present .map(types::ResponseId::ConnectorTransactionId) .ok_or(errors::ConnectorError::MissingConnectorTransactionID)?, - redirection_data, - mandate_reference: response - .payment_option - .and_then(|po| po.user_payment_option_id) - .map(|id| types::MandateReference { - connector_mandate_id: Some(id), - payment_method_id: None, - mandate_metadata: None, - }), + redirection_data: Box::new(redirection_data), + mandate_reference: Box::new( + response + .payment_option + .and_then(|po| po.user_payment_option_id) + .map(|id| types::MandateReference { + connector_mandate_id: Some(id), + payment_method_id: None, + mandate_metadata: None, + connector_mandate_request_reference_id: None, + }), + ), // we don't need to save session token for capture, void flow so ignoring if it is not present connector_metadata: if let Some(token) = response.session_token { Some( diff --git a/crates/router/src/connector/opayo.rs b/crates/router/src/connector/opayo.rs index cd1ef67f662e..a9fc439e54ce 100644 --- a/crates/router/src/connector/opayo.rs +++ b/crates/router/src/connector/opayo.rs @@ -1,8 +1,9 @@ mod transformers; -use std::fmt::Debug; - -use common_utils::request::RequestContent; +use common_utils::{ + request::RequestContent, + types::{AmountConvertor, MinorUnit, MinorUnitForConnector}, +}; use diesel_models::enums; use error_stack::{report, ResultExt}; use masking::ExposeInterface; @@ -10,14 +11,14 @@ use transformers as opayo; use crate::{ configs::settings, - connector::utils as connector_utils, + connector::{utils as connector_utils, utils::convert_amount}, core::errors::{self, CustomResult}, events::connector_api_logs::ConnectorEvent, headers, services::{ self, request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, + ConnectorIntegration, ConnectorSpecifications, ConnectorValidation, }, types::{ self, @@ -27,8 +28,18 @@ use crate::{ utils::BytesExt, }; -#[derive(Debug, Clone)] -pub struct Opayo; +#[derive(Clone)] +pub struct Opayo { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl Opayo { + pub fn new() -> &'static Self { + &Self { + amount_converter: &MinorUnitForConnector, + } + } +} impl api::Payment for Opayo {} impl api::PaymentSession for Opayo {} @@ -120,14 +131,17 @@ impl ConnectorCommon for Opayo { } impl ConnectorValidation for Opayo { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( connector_utils::construct_not_supported_error_report(capture_method, self.id()), ), @@ -197,7 +211,13 @@ impl ConnectorIntegration CustomResult { - let connector_req = opayo::OpayoPaymentsRequest::try_from(req)?; + let amount = convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + let connector_router_data = opayo::OpayoRouterData::from((amount, req)); + let connector_req = opayo::OpayoPaymentsRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -433,7 +453,13 @@ impl ConnectorIntegration, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_req = opayo::OpayoRefundRequest::try_from(req)?; + let refund_amount = convert_amount( + self.amount_converter, + req.request.minor_refund_amount, + req.request.currency, + )?; + let connector_router_data = opayo::OpayoRouterData::from((refund_amount, req)); + let connector_req = opayo::OpayoRefundRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -576,3 +602,5 @@ impl api::IncomingWebhook for Opayo { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for Opayo {} diff --git a/crates/router/src/connector/opayo/transformers.rs b/crates/router/src/connector/opayo/transformers.rs index dcb4e201e8bb..f55aa4325b06 100644 --- a/crates/router/src/connector/opayo/transformers.rs +++ b/crates/router/src/connector/opayo/transformers.rs @@ -1,3 +1,4 @@ +use common_utils::types::MinorUnit; use masking::Secret; use serde::{Deserialize, Serialize}; @@ -6,10 +7,24 @@ use crate::{ core::errors, types::{self, api, domain, storage::enums}, }; +#[derive(Debug, Serialize)] +pub struct OpayoRouterData { + pub amount: MinorUnit, + pub router_data: T, +} + +impl From<(MinorUnit, T)> for OpayoRouterData { + fn from((amount, router_data): (MinorUnit, T)) -> Self { + Self { + amount, + router_data, + } + } +} #[derive(Default, Debug, Serialize, Eq, PartialEq)] pub struct OpayoPaymentsRequest { - amount: i64, + amount: MinorUnit, card: OpayoCard, } @@ -23,9 +38,12 @@ pub struct OpayoCard { complete: bool, } -impl TryFrom<&types::PaymentsAuthorizeRouterData> for OpayoPaymentsRequest { +impl TryFrom<&OpayoRouterData<&types::PaymentsAuthorizeRouterData>> for OpayoPaymentsRequest { type Error = error_stack::Report; - fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result { + fn try_from( + item_data: &OpayoRouterData<&types::PaymentsAuthorizeRouterData>, + ) -> Result { + let item = item_data.router_data.clone(); match item.request.payment_method_data.clone() { domain::PaymentMethodData::Card(req_card) => { let card = OpayoCard { @@ -39,7 +57,7 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for OpayoPaymentsRequest { complete: item.request.is_auto_capture()?, }; Ok(Self { - amount: item.request.amount, + amount: item_data.amount, card, }) } @@ -53,12 +71,14 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for OpayoPaymentsRequest { | domain::PaymentMethodData::MandatePayment | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Opayo"), ) @@ -125,8 +145,8 @@ impl resource_id: types::ResponseId::ConnectorTransactionId( item.response.transaction_id.clone(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(item.response.transaction_id), @@ -142,14 +162,14 @@ impl // Type definition for RefundRequest #[derive(Default, Debug, Serialize)] pub struct OpayoRefundRequest { - pub amount: i64, + pub amount: MinorUnit, } -impl TryFrom<&types::RefundsRouterData> for OpayoRefundRequest { +impl TryFrom<&OpayoRouterData<&types::RefundsRouterData>> for OpayoRefundRequest { type Error = error_stack::Report; - fn try_from(item: &types::RefundsRouterData) -> Result { + fn try_from(item: &OpayoRouterData<&types::RefundsRouterData>) -> Result { Ok(Self { - amount: item.request.refund_amount, + amount: item.amount, }) } } diff --git a/crates/router/src/connector/opennode.rs b/crates/router/src/connector/opennode.rs index 0a1de1fdde2a..1c11da057122 100644 --- a/crates/router/src/connector/opennode.rs +++ b/crates/router/src/connector/opennode.rs @@ -16,7 +16,7 @@ use crate::{ services::{ self, request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, + ConnectorIntegration, ConnectorSpecifications, ConnectorValidation, }, types::{ self, @@ -449,3 +449,5 @@ impl api::IncomingWebhook for Opennode { Ok(Box::new(notif.status)) } } + +impl ConnectorSpecifications for Opennode {} diff --git a/crates/router/src/connector/opennode/transformers.rs b/crates/router/src/connector/opennode/transformers.rs index 8f1579ac1ff4..44315ac42fa8 100644 --- a/crates/router/src/connector/opennode/transformers.rs +++ b/crates/router/src/connector/opennode/transformers.rs @@ -138,8 +138,8 @@ impl let response_data = if attempt_status != OpennodePaymentStatus::Underpaid { Ok(types::PaymentsResponseData::TransactionResponse { resource_id: connector_id, - redirection_data: Some(redirection_data), - mandate_reference: None, + redirection_data: Box::new(Some(redirection_data)), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: item.response.data.order_id, @@ -258,7 +258,7 @@ fn get_crypto_specific_payment_data( let currency = item.router_data.request.currency.to_string(); let description = item.router_data.get_description()?; let auto_settle = true; - let success_url = item.router_data.get_return_url()?; + let success_url = item.router_data.request.get_router_return_url()?; let callback_url = item.router_data.request.get_webhook_url()?; let order_id = item.router_data.connector_request_reference_id.clone(); diff --git a/crates/router/src/connector/payme.rs b/crates/router/src/connector/payme.rs index 6bf26b4f60bd..10a0fe675621 100644 --- a/crates/router/src/connector/payme.rs +++ b/crates/router/src/connector/payme.rs @@ -23,7 +23,7 @@ use crate::{ }, events::connector_api_logs::ConnectorEvent, headers, - services::{self, request, ConnectorIntegration, ConnectorValidation}, + services::{self, request, ConnectorIntegration, ConnectorSpecifications, ConnectorValidation}, types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, @@ -138,14 +138,17 @@ impl ConnectorCommon for Payme { } impl ConnectorValidation for Payme { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( connector_utils::construct_not_supported_error_report(capture_method, self.id()), ), @@ -1280,7 +1283,7 @@ impl api::IncomingWebhook for Payme { Ok(api::disputes::DisputePayload { amount: webhook_object.price.to_string(), - currency: webhook_object.currency.to_string(), + currency: webhook_object.currency, dispute_stage: api_models::enums::DisputeStage::Dispute, connector_dispute_id: webhook_object.payme_transaction_id, connector_reason: None, @@ -1292,3 +1295,5 @@ impl api::IncomingWebhook for Payme { }) } } + +impl ConnectorSpecifications for Payme {} diff --git a/crates/router/src/connector/payme/transformers.rs b/crates/router/src/connector/payme/transformers.rs index e0ab598d76d6..cd8ba814eb7e 100644 --- a/crates/router/src/connector/payme/transformers.rs +++ b/crates/router/src/connector/payme/transformers.rs @@ -246,12 +246,15 @@ impl TryFrom<&PaymePaySaleResponse> for types::PaymentsResponseData { }; Ok(Self::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(value.payme_sale_id.clone()), - redirection_data, - mandate_reference: value.buyer_key.clone().map(|buyer_key| MandateReference { - connector_mandate_id: Some(buyer_key.expose()), - payment_method_id: None, - mandate_metadata: None, - }), + redirection_data: Box::new(redirection_data), + mandate_reference: Box::new(value.buyer_key.clone().map(|buyer_key| { + MandateReference { + connector_mandate_id: Some(buyer_key.expose()), + payment_method_id: None, + mandate_metadata: None, + connector_mandate_request_reference_id: None, + } + })), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -316,9 +319,9 @@ impl From<&SaleQuery> for types::PaymentsResponseData { fn from(value: &SaleQuery) -> Self { Self::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(value.sale_payme_id.clone()), - redirection_data: None, + redirection_data: Box::new(None), // mandate reference will be updated with webhooks only. That has been handled with PaymePaySaleResponse struct - mandate_reference: None, + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -372,7 +375,7 @@ impl TryFrom<&PaymeRouterData<&types::PaymentsPreProcessingRouterData>> for Gene sale_payment_method: SalePaymentMethod::try_from(&pmd)?, sale_type, transaction_id: item.router_data.payment_id.clone(), - sale_return_url: item.router_data.request.get_return_url()?, + sale_return_url: item.router_data.request.get_router_return_url()?, sale_callback_url: item.router_data.request.get_webhook_url()?, language: LANGUAGE.to_string(), services, @@ -403,6 +406,7 @@ impl TryFrom<&PaymentMethodData> for SalePaymentMethod { | domain::WalletData::MobilePayRedirect(_) | domain::WalletData::PaypalRedirect(_) | domain::WalletData::PaypalSdk(_) + | domain::WalletData::Paze(_) | domain::WalletData::SamsungPay(_) | domain::WalletData::TwintRedirect {} | domain::WalletData::VippsRedirect {} @@ -426,13 +430,15 @@ impl TryFrom<&PaymentMethodData> for SalePaymentMethod { | PaymentMethodData::MandatePayment | PaymentMethodData::Reward | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) | PaymentMethodData::GiftCard(_) | PaymentMethodData::CardRedirect(_) | PaymentMethodData::Upi(_) | PaymentMethodData::Voucher(_) | PaymentMethodData::OpenBanking(_) | PaymentMethodData::CardToken(_) - | PaymentMethodData::NetworkToken(_) => { + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()) } } @@ -534,8 +540,8 @@ impl resource_id: types::ResponseId::ConnectorTransactionId( item.response.payme_sale_id.to_owned(), ), - redirection_data: Some(services::RedirectForm::Payme), - mandate_reference: None, + redirection_data: Box::new(Some(services::RedirectForm::Payme)), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -573,6 +579,7 @@ impl merchant_identifier: None, required_billing_contact_fields: None, required_shipping_contact_fields: None, + recurring_payment_request: None, }, ), connector: "payme".to_string(), @@ -628,7 +635,7 @@ impl TryFrom<&PaymeRouterData<&types::PaymentsAuthorizeRouterData>> for MandateR sale_price: item.amount.to_owned(), transaction_id: item.router_data.payment_id.clone(), product_name, - sale_return_url: item.router_data.request.get_return_url()?, + sale_return_url: item.router_data.request.get_router_return_url()?, seller_payme_id, sale_callback_url: item.router_data.request.get_webhook_url()?, buyer_key: Secret::new(item.router_data.request.get_connector_mandate_id()?), @@ -673,14 +680,18 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for PayRequest { | PaymentMethodData::MandatePayment | PaymentMethodData::Reward | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) | PaymentMethodData::Upi(_) | PaymentMethodData::Voucher(_) | PaymentMethodData::GiftCard(_) | PaymentMethodData::OpenBanking(_) | PaymentMethodData::CardToken(_) - | PaymentMethodData::NetworkToken(_) => Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("payme"), - ))?, + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("payme"), + ))? + } } } } @@ -715,6 +726,9 @@ impl TryFrom<&types::PaymentsCompleteAuthorizeRouterData> for Pay3dsRequest { types::PaymentMethodToken::ApplePayDecrypt(_) => Err( unimplemented_payment_method!("Apple Pay", "Simplified", "Payme"), )?, + types::PaymentMethodToken::PazeDecrypt(_) => { + Err(unimplemented_payment_method!("Paze", "Payme"))? + } }; Ok(Self { buyer_email, @@ -734,12 +748,14 @@ impl TryFrom<&types::PaymentsCompleteAuthorizeRouterData> for Pay3dsRequest { | Some(PaymentMethodData::MandatePayment) | Some(PaymentMethodData::Reward) | Some(PaymentMethodData::RealTimePayment(_)) + | Some(PaymentMethodData::MobilePayment(_)) | Some(PaymentMethodData::Upi(_)) | Some(PaymentMethodData::Voucher(_)) | Some(PaymentMethodData::GiftCard(_)) | Some(PaymentMethodData::OpenBanking(_)) | Some(PaymentMethodData::CardToken(_)) | Some(PaymentMethodData::NetworkToken(_)) + | Some(PaymentMethodData::CardDetailsForNetworkTransactionId(_)) | None => { Err(errors::ConnectorError::NotImplemented("Tokenize Flow".to_string()).into()) } @@ -775,12 +791,14 @@ impl TryFrom<&types::TokenizationRouterData> for CaptureBuyerRequest { | PaymentMethodData::MandatePayment | PaymentMethodData::Reward | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) | PaymentMethodData::Upi(_) | PaymentMethodData::Voucher(_) | PaymentMethodData::GiftCard(_) | PaymentMethodData::OpenBanking(_) | PaymentMethodData::CardToken(_) - | PaymentMethodData::NetworkToken(_) => { + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented("Tokenize Flow".to_string()).into()) } } @@ -1100,8 +1118,8 @@ impl TryFrom> // Since we are not receiving payme_sale_id, we are not populating the transaction response Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::NoResponseId, - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, diff --git a/crates/router/src/connector/payone.rs b/crates/router/src/connector/payone.rs index 6bc04ed79c5f..58b179e57992 100644 --- a/crates/router/src/connector/payone.rs +++ b/crates/router/src/connector/payone.rs @@ -30,7 +30,7 @@ use crate::{ headers, services::{ request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, + ConnectorIntegration, ConnectorSpecifications, ConnectorValidation, }, types::{ self, @@ -429,3 +429,5 @@ impl ConnectorErrorTypeMapping for Payone { } } } + +impl ConnectorSpecifications for Payone {} diff --git a/crates/router/src/connector/paypal.rs b/crates/router/src/connector/paypal.rs index 5ea880c16b92..fb635708c23e 100644 --- a/crates/router/src/connector/paypal.rs +++ b/crates/router/src/connector/paypal.rs @@ -5,34 +5,38 @@ use base64::Engine; use common_utils::{ ext_traits::ByteSliceExt, request::RequestContent, - types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector}, + types::{AmountConvertor, MinorUnit, StringMajorUnit, StringMajorUnitForConnector}, }; use diesel_models::enums; use error_stack::ResultExt; use masking::{ExposeInterface, PeekInterface, Secret}; +use router_env::logger; #[cfg(feature = "payouts")] use router_env::{instrument, tracing}; use transformers as paypal; use self::transformers::{auth_headers, PaypalAuthResponse, PaypalMeta, PaypalWebhookEventType}; -use super::utils::{ConnectorErrorType, PaymentsCompleteAuthorizeRequestData}; +use super::utils::{ + ConnectorErrorType, PaymentMethodDataType, PaymentsAuthorizeRequestData, + PaymentsCompleteAuthorizeRequestData, +}; use crate::{ configs::settings, - connector::{ - utils as connector_utils, - utils::{to_connector_meta, ConnectorErrorTypeMapping, RefundsRequestData}, + connector::utils::{ + self as connector_utils, to_connector_meta, ConnectorErrorTypeMapping, RefundsRequestData, }, consts, core::{ errors::{self, CustomResult}, payments, }, + db::domain, events::connector_api_logs::ConnectorEvent, headers, services::{ self, request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, PaymentAction, + ConnectorIntegration, ConnectorSpecifications, ConnectorValidation, PaymentAction, }, types::{ self, @@ -71,6 +75,8 @@ impl api::Refund for Paypal {} impl api::RefundExecute for Paypal {} impl api::RefundSync for Paypal {} impl api::ConnectorVerifyWebhookSource for Paypal {} +impl api::PaymentPostSessionTokens for Paypal {} +impl api::PaymentSessionUpdate for Paypal {} impl api::Payouts for Paypal {} #[cfg(feature = "payouts")] @@ -314,19 +320,34 @@ impl ConnectorCommon for Paypal { } impl ConnectorValidation for Paypal { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( connector_utils::construct_not_implemented_error_report(capture_method, self.id()), ), } } + fn validate_mandate_payment( + &self, + pm_type: Option, + pm_data: domain::PaymentMethodData, + ) -> CustomResult<(), errors::ConnectorError> { + let mandate_supported_pmd = std::collections::HashSet::from([ + PaymentMethodDataType::Card, + PaymentMethodDataType::PaypalRedirect, + PaymentMethodDataType::PaypalSdk, + ]); + connector_utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) + } } impl @@ -479,7 +500,8 @@ impl ConnectorIntegration for Paypal { + fn get_headers( + &self, + req: &types::SetupMandateRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + fn get_url( + &self, + _req: &types::SetupMandateRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!( + "{}v3/vault/payment-tokens/", + self.base_url(connectors) + )) + } + fn get_request_body( + &self, + req: &types::SetupMandateRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_req = paypal::PaypalZeroMandateRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + fn build_request( &self, - _req: &types::RouterData< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - >, + 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: Response, + ) -> CustomResult { + let response: paypal::PaypalSetupMandatesResponse = res + .response + .parse_struct("PaypalSetupMandatesResponse") + .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: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl + ConnectorIntegration< + api::PostSessionTokens, + types::PaymentsPostSessionTokensData, + types::PaymentsResponseData, + > for Paypal +{ + fn get_headers( + &self, + req: &types::PaymentsPostSessionTokensRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + fn get_url( + &self, + _req: &types::PaymentsPostSessionTokensRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!("{}v2/checkout/orders", self.base_url(connectors))) + } + fn build_request( + &self, + req: &types::PaymentsPostSessionTokensRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsPostSessionTokensType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsPostSessionTokensType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsPostSessionTokensType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn get_request_body( + &self, + req: &types::PaymentsPostSessionTokensRouterData, _connectors: &settings::Connectors, + ) -> CustomResult { + let amount = connector_utils::convert_amount( + self.amount_converter, + req.request.amount, + req.request.currency, + )?; + let shipping_cost = connector_utils::convert_amount( + self.amount_converter, + req.request.shipping_cost.unwrap_or(MinorUnit::zero()), + req.request.currency, + )?; + let order_amount = connector_utils::convert_amount( + self.amount_converter, + req.request.order_amount, + req.request.currency, + )?; + let connector_router_data = paypal::PaypalRouterData::try_from(( + amount, + Some(shipping_cost), + None, + Some(order_amount), + req, + ))?; + let connector_req = paypal::PaypalPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn handle_response( + &self, + data: &types::PaymentsPostSessionTokensRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: paypal::PaypalRedirectResponse = res + .response + .parse_struct("PaypalRedirectResponse") + .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: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.get_order_error_response(res, event_builder) + } +} + +impl + ConnectorIntegration< + api::SdkSessionUpdate, + types::SdkPaymentsSessionUpdateData, + types::PaymentsResponseData, + > for Paypal +{ + fn get_headers( + &self, + req: &types::SdkSessionUpdateRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + req: &types::SdkSessionUpdateRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + let session_id = + req.request + .session_id + .clone() + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "session_id", + })?; + Ok(format!( + "{}v2/checkout/orders/{}", + self.base_url(connectors), + session_id + )) + } + + fn build_request( + &self, + req: &types::SdkSessionUpdateRouterData, + connectors: &settings::Connectors, ) -> CustomResult, errors::ConnectorError> { - Err( - errors::ConnectorError::NotImplemented("Setup Mandate flow for Paypal".to_string()) - .into(), - ) + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Patch) + .url(&types::SdkSessionUpdateType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::SdkSessionUpdateType::get_headers( + self, req, connectors, + )?) + .set_body(types::SdkSessionUpdateType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn get_request_body( + &self, + req: &types::SdkSessionUpdateRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let order_amount = connector_utils::convert_amount( + self.amount_converter, + req.request.order_amount, + req.request.currency, + )?; + let amount = connector_utils::convert_amount( + self.amount_converter, + req.request.amount, + req.request.currency, + )?; + let order_tax_amount = connector_utils::convert_amount( + self.amount_converter, + req.request.order_tax_amount, + req.request.currency, + )?; + let shipping_cost = connector_utils::convert_amount( + self.amount_converter, + req.request.shipping_cost.unwrap_or(MinorUnit::zero()), + req.request.currency, + )?; + let connector_router_data = paypal::PaypalRouterData::try_from(( + amount, + Some(shipping_cost), + Some(order_tax_amount), + Some(order_amount), + req, + ))?; + + let connector_req = paypal::PaypalUpdateOrderRequest::try_from(&connector_router_data)?; + // encode only for for urlencoded things. + Ok(RequestContent::Json(Box::new( + connector_req.get_inner_value(), + ))) + } + + fn handle_response( + &self, + data: &types::SdkSessionUpdateRouterData, + _event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + logger::debug!("Expected zero bytes response, skipped parsing of the response"); + // https://developer.paypal.com/docs/api/orders/v2/#orders_patch + // If 204 status code, then the session was updated successfully. + let status = if res.status_code == 204 { + enums::SessionUpdateStatus::Success + } else { + enums::SessionUpdateStatus::Failure + }; + Ok(types::SdkSessionUpdateRouterData { + response: Ok(types::PaymentsResponseData::SessionUpdateResponse { status }), + ..data.clone() + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) } } @@ -666,10 +981,26 @@ impl ConnectorIntegration CustomResult { - Ok(format!("{}v2/checkout/orders", self.base_url(connectors))) + match &req.request.payment_method_data { + domain::PaymentMethodData::Wallet(domain::WalletData::PaypalSdk( + paypal_wallet_data, + )) => { + let authorize_url = if req.request.is_auto_capture()? { + "capture".to_string() + } else { + "authorize".to_string() + }; + Ok(format!( + "{}v2/checkout/orders/{}/{authorize_url}", + self.base_url(connectors), + paypal_wallet_data.token + )) + } + _ => Ok(format!("{}v2/checkout/orders", self.base_url(connectors))), + } } fn get_request_body( @@ -682,7 +1013,13 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - Ok(Some( - services::RequestBuilder::new() + let payment_method_data = req.request.payment_method_data.clone(); + let req = match payment_method_data { + domain::PaymentMethodData::Wallet(domain::WalletData::PaypalSdk(_)) => { + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .headers(types::PaymentsAuthorizeType::get_headers( + self, req, connectors, + )?) + .build() + } + _ => services::RequestBuilder::new() .method(services::Method::Post) .url(&types::PaymentsAuthorizeType::get_url( self, req, connectors, )?) + .attach_default_headers() .headers(types::PaymentsAuthorizeType::get_headers( self, req, connectors, )?) @@ -705,7 +1055,9 @@ impl ConnectorIntegration { event_builder.map(|i| i.set_response_body(&response)); @@ -733,14 +1084,11 @@ impl ConnectorIntegration { event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::foreign_try_from(( - types::ResponseRouterData { - response, - data: data.clone(), - http_code: res.status_code, - }, - payment_method_data, - )) + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) } PaypalAuthResponse::PaypalThreeDsResponse(response) => { event_builder.map(|i| i.set_response_body(&response)); @@ -873,14 +1221,14 @@ impl status: storage_enums::AttemptStatus::AuthenticationSuccessful, response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::NoResponseId, - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, - }), + charge_id: None, + }), ..data.clone() }) } @@ -924,8 +1272,8 @@ impl status: storage_enums::AttemptStatus::AuthenticationSuccessful, response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::NoResponseId, - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -1187,7 +1535,8 @@ impl ConnectorIntegration { pub amount: StringMajorUnit, + pub shipping_cost: Option, + pub order_tax_amount: Option, + pub order_amount: Option, pub router_data: T, } -impl TryFrom<(StringMajorUnit, T)> for PaypalRouterData { +impl + TryFrom<( + StringMajorUnit, + Option, + Option, + Option, + T, + )> for PaypalRouterData +{ type Error = error_stack::Report; - fn try_from((amount, item): (StringMajorUnit, T)) -> Result { + fn try_from( + (amount, shipping_cost, order_tax_amount, order_amount, item): ( + StringMajorUnit, + Option, + Option, + Option, + T, + ), + ) -> Result { Ok(Self { amount, + shipping_cost, + order_tax_amount, + order_amount, router_data: item, }) } @@ -55,6 +78,8 @@ pub mod auth_headers { pub const PAYPAL_AUTH_ASSERTION: &str = "PayPal-Auth-Assertion"; } +const ORDER_QUANTITY: u16 = 1; + #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] #[serde(rename_all = "UPPERCASE")] pub enum PaypalPaymentIntent { @@ -86,14 +111,93 @@ impl From<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for OrderReque currency_code: item.router_data.request.currency, value: item.amount.clone(), }, + tax_total: None, + shipping: Some(OrderAmount { + currency_code: item.router_data.request.currency, + value: item + .shipping_cost + .clone() + .unwrap_or(StringMajorUnit::zero()), + }), }, } } } +impl TryFrom<&PaypalRouterData<&types::PaymentsPostSessionTokensRouterData>> + for OrderRequestAmount +{ + type Error = error_stack::Report; + fn try_from( + item: &PaypalRouterData<&types::PaymentsPostSessionTokensRouterData>, + ) -> Result { + Ok(Self { + currency_code: item.router_data.request.currency, + value: item.amount.clone(), + breakdown: AmountBreakdown { + item_total: OrderAmount { + currency_code: item.router_data.request.currency, + value: item.order_amount.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "order_amount", + }, + )?, + }, + tax_total: None, + shipping: Some(OrderAmount { + currency_code: item.router_data.request.currency, + value: item + .shipping_cost + .clone() + .unwrap_or(StringMajorUnit::zero()), + }), + }, + }) + } +} + +impl TryFrom<&PaypalRouterData<&types::SdkSessionUpdateRouterData>> for OrderRequestAmount { + type Error = error_stack::Report; + fn try_from( + item: &PaypalRouterData<&types::SdkSessionUpdateRouterData>, + ) -> Result { + Ok(Self { + currency_code: item.router_data.request.currency, + value: item.amount.clone(), + breakdown: AmountBreakdown { + item_total: OrderAmount { + currency_code: item.router_data.request.currency, + value: item.order_amount.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "order_amount", + }, + )?, + }, + tax_total: Some(OrderAmount { + currency_code: item.router_data.request.currency, + value: item.order_tax_amount.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "order_tax_amount", + }, + )?, + }), + shipping: Some(OrderAmount { + currency_code: item.router_data.request.currency, + value: item + .shipping_cost + .clone() + .unwrap_or(StringMajorUnit::zero()), + }), + }, + }) + } +} + #[derive(Default, Debug, Serialize, Deserialize, Eq, PartialEq)] pub struct AmountBreakdown { item_total: OrderAmount, + tax_total: Option, + shipping: Option, } #[derive(Default, Debug, Serialize, Eq, PartialEq)] @@ -118,6 +222,7 @@ pub struct ItemDetails { name: String, quantity: u16, unit_amount: OrderAmount, + tax: Option, } impl From<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for ItemDetails { @@ -127,16 +232,72 @@ impl From<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for ItemDetail "Payment for invoice {}", item.router_data.connector_request_reference_id ), - quantity: 1, + quantity: ORDER_QUANTITY, unit_amount: OrderAmount { currency_code: item.router_data.request.currency, value: item.amount.clone(), }, + tax: None, } } } -#[derive(Default, Debug, Serialize, Eq, PartialEq)] +impl TryFrom<&PaypalRouterData<&types::PaymentsPostSessionTokensRouterData>> for ItemDetails { + type Error = error_stack::Report; + fn try_from( + item: &PaypalRouterData<&types::PaymentsPostSessionTokensRouterData>, + ) -> Result { + Ok(Self { + name: format!( + "Payment for invoice {}", + item.router_data.connector_request_reference_id + ), + quantity: ORDER_QUANTITY, + unit_amount: OrderAmount { + currency_code: item.router_data.request.currency, + value: item.order_amount.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "order_amount", + }, + )?, + }, + tax: None, + }) + } +} + +impl TryFrom<&PaypalRouterData<&types::SdkSessionUpdateRouterData>> for ItemDetails { + type Error = error_stack::Report; + fn try_from( + item: &PaypalRouterData<&types::SdkSessionUpdateRouterData>, + ) -> Result { + Ok(Self { + name: format!( + "Payment for invoice {}", + item.router_data.connector_request_reference_id + ), + quantity: ORDER_QUANTITY, + unit_amount: OrderAmount { + currency_code: item.router_data.request.currency, + value: item.order_amount.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "order_amount", + }, + )?, + }, + tax: Some(OrderAmount { + currency_code: item.router_data.request.currency, + value: item.order_tax_amount.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "order_tax_amount", + }, + )?, + }), + }) + } +} + +#[derive(Default, Debug, Serialize, Eq, PartialEq, Deserialize)] pub struct Address { address_line_1: Option>, postal_code: Option>, @@ -165,24 +326,85 @@ impl From<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for ShippingAd } } +impl From<&PaypalRouterData<&types::PaymentsPostSessionTokensRouterData>> for ShippingAddress { + fn from(item: &PaypalRouterData<&types::PaymentsPostSessionTokensRouterData>) -> Self { + Self { + address: get_address_info(item.router_data.get_optional_shipping()), + name: Some(ShippingName { + full_name: item + .router_data + .get_optional_shipping() + .and_then(|inner_data| inner_data.address.as_ref()) + .and_then(|inner_data| inner_data.first_name.clone()), + }), + } + } +} + +#[derive(Debug, Serialize, PartialEq, Eq)] +pub struct PaypalUpdateOrderRequest(Vec); + +impl PaypalUpdateOrderRequest { + pub fn get_inner_value(self) -> Vec { + self.0 + } +} + +#[derive(Debug, Serialize, PartialEq, Eq)] +pub struct Operation { + pub op: PaypalOperationType, + pub path: String, + pub value: Value, +} + +#[derive(Debug, Serialize, PartialEq, Eq, Clone)] +#[serde(rename_all = "lowercase")] +pub enum PaypalOperationType { + Add, + Remove, + Replace, + Move, + Copy, + Test, +} + +#[derive(Debug, Serialize, PartialEq, Eq)] +#[serde(untagged)] +pub enum Value { + Amount(OrderRequestAmount), + Items(Vec), +} + #[derive(Default, Debug, Serialize, Eq, PartialEq)] pub struct ShippingName { full_name: Option>, } #[derive(Debug, Serialize)] -pub struct CardRequest { +pub struct CardRequestStruct { billing_address: Option
, expiry: Option>, - name: Secret, + name: Option>, number: Option, security_code: Option>, - attributes: Option, + attributes: Option, } +#[derive(Debug, Serialize, Deserialize)] +pub struct VaultStruct { + vault_id: Secret, +} + +#[derive(Debug, Serialize)] +#[serde(untagged)] +pub enum CardRequest { + CardRequestStruct(CardRequestStruct), + CardVaultStruct(VaultStruct), +} #[derive(Debug, Serialize)] -pub struct ThreeDsSetting { - verification: ThreeDsMethod, +pub struct CardRequestAttributes { + vault: Option, + verification: Option, } #[derive(Debug, Serialize)] @@ -203,7 +425,7 @@ pub struct RedirectRequest { experience_context: ContextStruct, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct ContextStruct { return_url: Option, cancel_url: Option, @@ -211,13 +433,13 @@ pub struct ContextStruct { shipping_preference: ShippingPreference, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Deserialize)] pub enum UserAction { #[serde(rename = "PAY_NOW")] PayNow, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Deserialize)] pub enum ShippingPreference { #[serde(rename = "SET_PROVIDED_ADDRESS")] SetProvidedAddress, @@ -226,8 +448,70 @@ pub enum ShippingPreference { } #[derive(Debug, Serialize)] -pub struct PaypalRedirectionRequest { +#[serde(untagged)] +pub enum PaypalRedirectionRequest { + PaypalRedirectionStruct(PaypalRedirectionStruct), + PaypalVaultStruct(VaultStruct), +} + +#[derive(Debug, Serialize)] +pub struct PaypalRedirectionStruct { experience_context: ContextStruct, + attributes: Option, +} +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Attributes { + vault: PaypalVault, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct PaypalRedirectionResponse { + attributes: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct EpsRedirectionResponse { + name: Option>, + country_code: Option, + bic: Option>, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct IdealRedirectionResponse { + name: Option>, + country_code: Option, + bic: Option>, + iban_last_chars: Option>, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct AttributeResponse { + vault: PaypalVaultResponse, +} +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct PaypalVault { + store_in_vault: StoreInVault, + usage_type: UsageType, +} +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct PaypalVaultResponse { + id: String, + status: String, + customer: CustomerId, +} +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct CustomerId { + id: String, +} +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum StoreInVault { + OnSuccess, +} +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum UsageType { + Merchant, } #[derive(Debug, Serialize)] @@ -240,6 +524,18 @@ pub enum PaymentSourceItem { Giropay(RedirectRequest), Sofort(RedirectRequest), } +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct CardVaultResponse { + attributes: Option, +} +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "lowercase")] +pub enum PaymentSourceItemResponse { + Card(CardVaultResponse), + Paypal(PaypalRedirectionResponse), + Eps(EpsRedirectionResponse), + Ideal(IdealRedirectionResponse), +} #[derive(Debug, Serialize)] pub struct PaypalPaymentsRequest { @@ -248,7 +544,134 @@ pub struct PaypalPaymentsRequest { payment_source: Option, } -fn get_address_info(payment_address: Option<&api_models::payments::Address>) -> Option
{ +#[derive(Debug, Serialize)] +pub struct PaypalZeroMandateRequest { + payment_source: ZeroMandateSourceItem, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum ZeroMandateSourceItem { + Card(CardMandateRequest), + Paypal(PaypalMandateStruct), +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct PaypalMandateStruct { + experience_context: Option, + usage_type: UsageType, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct CardMandateRequest { + billing_address: Option
, + expiry: Option>, + name: Option>, + number: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct PaypalSetupMandatesResponse { + id: String, + customer: Customer, + payment_source: ZeroMandateSourceItem, + links: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Customer { + id: String, +} + +impl + TryFrom< + types::ResponseRouterData, + > for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + PaypalSetupMandatesResponse, + T, + types::PaymentsResponseData, + >, + ) -> Result { + let info_response = item.response; + + let mandate_reference = Some(MandateReference { + connector_mandate_id: Some(info_response.id.clone()), + payment_method_id: None, + mandate_metadata: None, + connector_mandate_request_reference_id: None, + }); + // https://developer.paypal.com/docs/api/payment-tokens/v3/#payment-tokens_create + // If 201 status code, then order is captured, other status codes are handled by the error handler + let status = if item.http_code == 201 { + enums::AttemptStatus::Charged + } else { + enums::AttemptStatus::Failure + }; + Ok(Self { + status, + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId(info_response.id.clone()), + redirection_data: Box::new(None), + mandate_reference: Box::new(mandate_reference), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(info_response.id.clone()), + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } +} +impl TryFrom<&types::SetupMandateRouterData> for PaypalZeroMandateRequest { + type Error = error_stack::Report; + fn try_from(item: &types::SetupMandateRouterData) -> Result { + let payment_source = match item.request.payment_method_data.clone() { + domain::PaymentMethodData::Card(ccard) => { + ZeroMandateSourceItem::Card(CardMandateRequest { + billing_address: get_address_info(item.get_optional_billing()), + expiry: Some(ccard.get_expiry_date_as_yyyymm("-")), + name: item.get_optional_billing_full_name(), + number: Some(ccard.card_number), + }) + } + + domain::PaymentMethodData::Wallet(_) + | 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::RealTimePayment(_) + | domain::PaymentMethodData::Upi(_) + | domain::PaymentMethodData::Voucher(_) + | domain::PaymentMethodData::GiftCard(_) + | domain::PaymentMethodData::CardToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::OpenBanking(_) + | domain::PaymentMethodData::MobilePayment(_) => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Paypal"), + ))? + } + }; + + Ok(Self { payment_source }) + } +} + +fn get_address_info( + payment_address: Option<&hyperswitch_domain_models::address::Address>, +) -> Option
{ let address = payment_address.and_then(|payment_address| payment_address.address.as_ref()); match address { Some(address) => address.get_optional_country().map(|country| Address { @@ -366,6 +789,102 @@ fn get_payee(auth_type: &PaypalAuthType) -> Option { }) } +impl TryFrom<&PaypalRouterData<&types::PaymentsPostSessionTokensRouterData>> + for PaypalPaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &PaypalRouterData<&types::PaymentsPostSessionTokensRouterData>, + ) -> Result { + let intent = if item.router_data.request.is_auto_capture()? { + PaypalPaymentIntent::Capture + } else { + PaypalPaymentIntent::Authorize + }; + let paypal_auth: PaypalAuthType = + PaypalAuthType::try_from(&item.router_data.connector_auth_type)?; + let payee = get_payee(&paypal_auth); + + let amount = OrderRequestAmount::try_from(item)?; + let connector_request_reference_id = + item.router_data.connector_request_reference_id.clone(); + + let shipping_address = ShippingAddress::from(item); + let item_details = vec![ItemDetails::try_from(item)?]; + + let purchase_units = vec![PurchaseUnitRequest { + reference_id: Some(connector_request_reference_id.clone()), + custom_id: item.router_data.request.merchant_order_reference_id.clone(), + invoice_id: Some(connector_request_reference_id), + amount, + payee, + shipping: Some(shipping_address), + items: item_details, + }]; + let payment_source = Some(PaymentSourceItem::Paypal( + PaypalRedirectionRequest::PaypalRedirectionStruct(PaypalRedirectionStruct { + experience_context: ContextStruct { + return_url: item.router_data.request.router_return_url.clone(), + cancel_url: item.router_data.request.router_return_url.clone(), + shipping_preference: ShippingPreference::GetFromFile, + user_action: Some(UserAction::PayNow), + }, + attributes: match item.router_data.request.setup_future_usage { + Some(setup_future_usage) => match setup_future_usage { + enums::FutureUsage::OffSession => Some(Attributes { + vault: PaypalVault { + store_in_vault: StoreInVault::OnSuccess, + usage_type: UsageType::Merchant, + }, + }), + enums::FutureUsage::OnSession => None, + }, + None => None, + }, + }), + )); + + Ok(Self { + intent, + purchase_units, + payment_source, + }) + } +} + +impl TryFrom<&PaypalRouterData<&types::SdkSessionUpdateRouterData>> for PaypalUpdateOrderRequest { + type Error = error_stack::Report; + + fn try_from( + item: &PaypalRouterData<&types::SdkSessionUpdateRouterData>, + ) -> Result { + let op = PaypalOperationType::Replace; + + // Create separate paths for amount and items + let reference_id = &item.router_data.connector_request_reference_id; + + let amount_path = format!("/purchase_units/@reference_id=='{}'/amount", reference_id); + let items_path = format!("/purchase_units/@reference_id=='{}'/items", reference_id); + + let amount_value = Value::Amount(OrderRequestAmount::try_from(item)?); + + let items_value = Value::Items(vec![ItemDetails::try_from(item)?]); + + Ok(Self(vec![ + Operation { + op: op.clone(), + path: amount_path, + value: amount_value, + }, + Operation { + op, + path: items_path, + value: items_value, + }, + ])) + } +} + impl TryFrom<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for PaypalPaymentsRequest { type Error = error_stack::Report; fn try_from( @@ -404,26 +923,36 @@ impl TryFrom<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for PaypalP let card = item.router_data.request.get_card()?; let expiry = Some(card.get_expiry_date_as_yyyymm("-")); - let attributes = match item.router_data.auth_type { - enums::AuthenticationType::ThreeDs => Some(ThreeDsSetting { - verification: ThreeDsMethod { - method: ThreeDsType::ScaAlways, - }, + let verification = match item.router_data.auth_type { + enums::AuthenticationType::ThreeDs => Some(ThreeDsMethod { + method: ThreeDsType::ScaAlways, }), enums::AuthenticationType::NoThreeDs => None, }; - let payment_source = Some(PaymentSourceItem::Card(CardRequest { - billing_address: get_address_info(item.router_data.get_optional_billing()), - expiry, - name: item - .router_data - .get_optional_billing_full_name() - .unwrap_or(Secret::new("".to_string())), - number: Some(ccard.card_number.clone()), - security_code: Some(ccard.card_cvc.clone()), - attributes, - })); + let payment_source = Some(PaymentSourceItem::Card(CardRequest::CardRequestStruct( + CardRequestStruct { + billing_address: get_address_info(item.router_data.get_optional_billing()), + expiry, + name: item.router_data.get_optional_billing_full_name(), + number: Some(ccard.card_number.clone()), + security_code: Some(ccard.card_cvc.clone()), + attributes: Some(CardRequestAttributes { + vault: match item.router_data.request.setup_future_usage { + Some(setup_future_usage) => match setup_future_usage { + enums::FutureUsage::OffSession => Some(PaypalVault { + store_in_vault: StoreInVault::OnSuccess, + usage_type: UsageType::Merchant, + }), + + enums::FutureUsage::OnSession => None, + }, + None => None, + }, + verification, + }), + }, + ))); Ok(Self { intent, @@ -433,23 +962,46 @@ impl TryFrom<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for PaypalP } domain::PaymentMethodData::Wallet(ref wallet_data) => match wallet_data { domain::WalletData::PaypalRedirect(_) => { - let payment_source = - Some(PaymentSourceItem::Paypal(PaypalRedirectionRequest { - experience_context: ContextStruct { - return_url: item.router_data.request.complete_authorize_url.clone(), - cancel_url: item.router_data.request.complete_authorize_url.clone(), - shipping_preference: if item - .router_data - .get_optional_shipping_country() - .is_some() - { - ShippingPreference::SetProvidedAddress - } else { - ShippingPreference::GetFromFile + let payment_source = Some(PaymentSourceItem::Paypal( + PaypalRedirectionRequest::PaypalRedirectionStruct( + PaypalRedirectionStruct { + experience_context: ContextStruct { + return_url: item + .router_data + .request + .complete_authorize_url + .clone(), + cancel_url: item + .router_data + .request + .complete_authorize_url + .clone(), + shipping_preference: if item + .router_data + .get_optional_shipping() + .is_some() + { + ShippingPreference::SetProvidedAddress + } else { + ShippingPreference::GetFromFile + }, + user_action: Some(UserAction::PayNow), + }, + attributes: match item.router_data.request.setup_future_usage { + Some(setup_future_usage) => match setup_future_usage { + enums::FutureUsage::OffSession => Some(Attributes { + vault: PaypalVault { + store_in_vault: StoreInVault::OnSuccess, + usage_type: UsageType::Merchant, + }, + }), + enums::FutureUsage::OnSession => None, + }, + None => None, }, - user_action: Some(UserAction::PayNow), }, - })); + ), + )); Ok(Self { intent, @@ -458,15 +1010,30 @@ impl TryFrom<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for PaypalP }) } domain::WalletData::PaypalSdk(_) => { - let payment_source = - Some(PaymentSourceItem::Paypal(PaypalRedirectionRequest { - experience_context: ContextStruct { - return_url: None, - cancel_url: None, - shipping_preference: ShippingPreference::GetFromFile, - user_action: Some(UserAction::PayNow), + let payment_source = Some(PaymentSourceItem::Paypal( + PaypalRedirectionRequest::PaypalRedirectionStruct( + PaypalRedirectionStruct { + experience_context: ContextStruct { + return_url: None, + cancel_url: None, + shipping_preference: ShippingPreference::GetFromFile, + user_action: Some(UserAction::PayNow), + }, + attributes: match item.router_data.request.setup_future_usage { + Some(setup_future_usage) => match setup_future_usage { + enums::FutureUsage::OffSession => Some(Attributes { + vault: PaypalVault { + store_in_vault: StoreInVault::OnSuccess, + usage_type: UsageType::Merchant, + }, + }), + enums::FutureUsage::OnSession => None, + }, + None => None, + }, }, - })); + ), + )); Ok(Self { intent, @@ -498,7 +1065,8 @@ impl TryFrom<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for PaypalP | domain::WalletData::WeChatPayQr(_) | domain::WalletData::CashappQr(_) | domain::WalletData::SwishQr(_) - | domain::WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( + | domain::WalletData::Mifinity(_) + | domain::WalletData::Paze(_) => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Paypal"), ))?, }, @@ -536,18 +1104,142 @@ impl TryFrom<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for PaypalP Self::try_from(giftcard_data.as_ref()) } domain::PaymentMethodData::MandatePayment => { - Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("Paypal"), - ) - .into()) + let payment_method_type = item + .router_data + .get_recurring_mandate_payment_data()? + .payment_method_type + .ok_or_else(missing_field_err("payment_method_type"))?; + + let connector_mandate_id = item.router_data.request.connector_mandate_id().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "connector_mandate_id", + }, + )?; + + let payment_source = match payment_method_type { + enums::PaymentMethodType::Credit | enums::PaymentMethodType::Debit => Ok(Some( + PaymentSourceItem::Card(CardRequest::CardVaultStruct(VaultStruct { + vault_id: connector_mandate_id.into(), + })), + )), + enums::PaymentMethodType::Paypal => Ok(Some(PaymentSourceItem::Paypal( + PaypalRedirectionRequest::PaypalVaultStruct(VaultStruct { + vault_id: connector_mandate_id.into(), + }), + ))), + enums::PaymentMethodType::Ach + | enums::PaymentMethodType::Affirm + | enums::PaymentMethodType::AfterpayClearpay + | enums::PaymentMethodType::Alfamart + | enums::PaymentMethodType::AliPay + | enums::PaymentMethodType::AliPayHk + | enums::PaymentMethodType::Alma + | enums::PaymentMethodType::ApplePay + | enums::PaymentMethodType::Atome + | enums::PaymentMethodType::Bacs + | enums::PaymentMethodType::BancontactCard + | enums::PaymentMethodType::Becs + | enums::PaymentMethodType::Benefit + | enums::PaymentMethodType::Bizum + | enums::PaymentMethodType::Blik + | enums::PaymentMethodType::Boleto + | enums::PaymentMethodType::BcaBankTransfer + | enums::PaymentMethodType::BniVa + | enums::PaymentMethodType::BriVa + | enums::PaymentMethodType::CardRedirect + | enums::PaymentMethodType::CimbVa + | enums::PaymentMethodType::ClassicReward + | enums::PaymentMethodType::CryptoCurrency + | enums::PaymentMethodType::Cashapp + | enums::PaymentMethodType::Dana + | enums::PaymentMethodType::DanamonVa + | enums::PaymentMethodType::DirectCarrierBilling + | enums::PaymentMethodType::DuitNow + | enums::PaymentMethodType::Efecty + | enums::PaymentMethodType::Eps + | enums::PaymentMethodType::Fps + | enums::PaymentMethodType::Evoucher + | enums::PaymentMethodType::Giropay + | enums::PaymentMethodType::Givex + | enums::PaymentMethodType::GooglePay + | enums::PaymentMethodType::GoPay + | enums::PaymentMethodType::Gcash + | enums::PaymentMethodType::Ideal + | enums::PaymentMethodType::Interac + | enums::PaymentMethodType::Indomaret + | enums::PaymentMethodType::Klarna + | enums::PaymentMethodType::KakaoPay + | enums::PaymentMethodType::LocalBankRedirect + | enums::PaymentMethodType::MandiriVa + | enums::PaymentMethodType::Knet + | enums::PaymentMethodType::MbWay + | enums::PaymentMethodType::MobilePay + | enums::PaymentMethodType::Momo + | enums::PaymentMethodType::MomoAtm + | enums::PaymentMethodType::Multibanco + | enums::PaymentMethodType::OnlineBankingThailand + | enums::PaymentMethodType::OnlineBankingCzechRepublic + | enums::PaymentMethodType::OnlineBankingFinland + | enums::PaymentMethodType::OnlineBankingFpx + | enums::PaymentMethodType::OnlineBankingPoland + | enums::PaymentMethodType::OnlineBankingSlovakia + | enums::PaymentMethodType::OpenBankingPIS + | enums::PaymentMethodType::Oxxo + | enums::PaymentMethodType::PagoEfectivo + | enums::PaymentMethodType::PermataBankTransfer + | enums::PaymentMethodType::OpenBankingUk + | enums::PaymentMethodType::PayBright + | enums::PaymentMethodType::Pix + | enums::PaymentMethodType::PaySafeCard + | enums::PaymentMethodType::Przelewy24 + | enums::PaymentMethodType::PromptPay + | enums::PaymentMethodType::Pse + | enums::PaymentMethodType::RedCompra + | enums::PaymentMethodType::RedPagos + | enums::PaymentMethodType::SamsungPay + | enums::PaymentMethodType::Sepa + | enums::PaymentMethodType::Sofort + | enums::PaymentMethodType::Swish + | enums::PaymentMethodType::TouchNGo + | enums::PaymentMethodType::Trustly + | enums::PaymentMethodType::Twint + | enums::PaymentMethodType::UpiCollect + | enums::PaymentMethodType::UpiIntent + | enums::PaymentMethodType::Vipps + | enums::PaymentMethodType::VietQr + | enums::PaymentMethodType::Venmo + | enums::PaymentMethodType::Walley + | enums::PaymentMethodType::WeChatPay + | enums::PaymentMethodType::SevenEleven + | enums::PaymentMethodType::Lawson + | enums::PaymentMethodType::MiniStop + | enums::PaymentMethodType::FamilyMart + | enums::PaymentMethodType::Seicomart + | enums::PaymentMethodType::PayEasy + | enums::PaymentMethodType::LocalBankTransfer + | enums::PaymentMethodType::Mifinity + | enums::PaymentMethodType::Paze => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("paypal"), + )) + } + }; + + Ok(Self { + intent, + purchase_units, + payment_source: payment_source?, + }) } domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Crypto(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Paypal"), ) @@ -580,6 +1272,7 @@ impl TryFrom<&domain::PayLaterData> for PaypalPaymentsRequest { match value { domain::PayLaterData::KlarnaRedirect { .. } | domain::PayLaterData::KlarnaSdk { .. } + | domain::PayLaterData::KlarnaCheckout {} | domain::PayLaterData::AffirmRedirect {} | domain::PayLaterData::AfterpayClearpayRedirect { .. } | domain::PayLaterData::PayBrightRedirect {} @@ -964,6 +1657,7 @@ pub struct PaypalOrdersResponse { intent: PaypalPaymentIntent, status: PaypalOrderStatus, purchase_units: Vec, + payment_source: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -984,6 +1678,7 @@ pub struct PaypalRedirectResponse { status: PaypalOrderStatus, purchase_units: Vec, links: Vec, + payment_source: Option, } // Note: Don't change order of deserialization of variant, priority is in descending order @@ -1038,6 +1733,7 @@ pub struct PaypalMeta { pub capture_id: Option, pub psync_flow: PaypalPaymentIntent, pub next_action: Option, + pub order_id: Option, } fn get_id_based_on_intent( @@ -1092,6 +1788,7 @@ impl capture_id: Some(id), psync_flow: item.response.intent.clone(), next_action: None, + order_id: None, }), types::ResponseId::ConnectorTransactionId(item.response.id.clone()), ), @@ -1102,6 +1799,7 @@ impl capture_id: None, psync_flow: item.response.intent.clone(), next_action: None, + order_id: None, }), types::ResponseId::ConnectorTransactionId(item.response.id.clone()), ), @@ -1134,8 +1832,25 @@ impl status, response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: order_id, - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(Some(MandateReference { + connector_mandate_id: match item.response.payment_source.clone() { + Some(paypal_source) => match paypal_source { + PaymentSourceItemResponse::Paypal(paypal_source) => { + paypal_source.attributes.map(|attr| attr.vault.id) + } + PaymentSourceItemResponse::Card(card) => { + card.attributes.map(|attr| attr.vault.id) + } + PaymentSourceItemResponse::Eps(_) + | PaymentSourceItemResponse::Ideal(_) => None, + }, + None => None, + }, + payment_method_id: None, + mandate_metadata: None, + connector_mandate_request_reference_id: None, + })), connector_metadata: Some(connector_meta), network_txn_id: None, connector_response_reference_id: purchase_units @@ -1250,17 +1965,18 @@ impl capture_id: None, psync_flow: item.response.intent, next_action, + order_id: None, }); let purchase_units = item.response.purchase_units.first(); Ok(Self { status, response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()), - redirection_data: Some(services::RedirectForm::from(( + redirection_data: Box::new(Some(services::RedirectForm::from(( link.ok_or(errors::ConnectorError::ResponseDeserializationFailed)?, services::Method::Get, - ))), - mandate_reference: None, + )))), + mandate_reference: Box::new(None), connector_metadata: Some(connector_meta), network_txn_id: None, connector_response_reference_id: Some( @@ -1274,18 +1990,24 @@ impl } } -impl - ForeignTryFrom<( - types::ResponseRouterData, - domain::PaymentMethodData, - )> for types::RouterData +impl + TryFrom< + types::ResponseRouterData< + api::Authorize, + PaypalRedirectResponse, + types::PaymentsAuthorizeData, + types::PaymentsResponseData, + >, + > for types::PaymentsAuthorizeRouterData { type Error = error_stack::Report; - fn foreign_try_from( - (item, payment_method_data): ( - types::ResponseRouterData, - domain::PaymentMethodData, - ), + fn try_from( + item: types::ResponseRouterData< + api::Authorize, + PaypalRedirectResponse, + types::PaymentsAuthorizeData, + types::PaymentsResponseData, + >, ) -> Result { let status = storage_enums::AttemptStatus::foreign_from(( item.response.clone().status, @@ -1293,32 +2015,23 @@ impl )); let link = get_redirect_url(item.response.links.clone())?; - // For Paypal SDK flow, we need to trigger SDK client and then complete authorize - let next_action = - if let domain::PaymentMethodData::Wallet(domain::WalletData::PaypalSdk(_)) = - payment_method_data - { - Some(api_models::payments::NextActionCall::CompleteAuthorize) - } else { - None - }; - let connector_meta = serde_json::json!(PaypalMeta { authorize_id: None, capture_id: None, psync_flow: item.response.intent, - next_action, + next_action: None, + order_id: None, }); let purchase_units = item.response.purchase_units.first(); Ok(Self { status, response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()), - redirection_data: Some(services::RedirectForm::from(( + redirection_data: Box::new(Some(services::RedirectForm::from(( link.ok_or(errors::ConnectorError::ResponseDeserializationFailed)?, services::Method::Get, - ))), - mandate_reference: None, + )))), + mandate_reference: Box::new(None), connector_metadata: Some(connector_meta), network_txn_id: None, connector_response_reference_id: Some( @@ -1332,6 +2045,59 @@ impl } } +impl + TryFrom< + types::ResponseRouterData< + api::PostSessionTokens, + PaypalRedirectResponse, + types::PaymentsPostSessionTokensData, + types::PaymentsResponseData, + >, + > for types::PaymentsPostSessionTokensRouterData +{ + type Error = error_stack::Report; + + fn try_from( + item: types::ResponseRouterData< + api::PostSessionTokens, + PaypalRedirectResponse, + types::PaymentsPostSessionTokensData, + types::PaymentsResponseData, + >, + ) -> Result { + let status = storage_enums::AttemptStatus::foreign_from(( + item.response.clone().status, + item.response.intent.clone(), + )); + + // For Paypal SDK flow, we need to trigger SDK client and then Confirm + let next_action = Some(api_models::payments::NextActionCall::Confirm); + + let connector_meta = serde_json::json!(PaypalMeta { + authorize_id: None, + capture_id: None, + psync_flow: item.response.intent, + next_action, + order_id: Some(item.response.id.clone()), + }); + + Ok(Self { + status, + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::NoResponseId, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: Some(connector_meta), + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } +} + impl TryFrom< types::ResponseRouterData< @@ -1357,8 +2123,8 @@ impl status: storage_enums::AttemptStatus::AuthenticationPending, response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(item.response.id), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -1394,6 +2160,7 @@ impl capture_id: None, psync_flow: PaypalPaymentIntent::Authenticate, // when there is no capture or auth id present next_action: None, + order_id: None, }); let status = storage_enums::AttemptStatus::foreign_from(( @@ -1406,11 +2173,11 @@ impl status, response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(item.response.id), - redirection_data: Some(paypal_threeds_link(( + redirection_data: Box::new(Some(paypal_threeds_link(( link, item.data.request.complete_authorize_url.clone(), - ))?), - mandate_reference: None, + ))?)), + mandate_reference: Box::new(None), connector_metadata: Some(connector_meta), network_txn_id: None, connector_response_reference_id: None, @@ -1474,8 +2241,8 @@ impl .order_id .clone(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: item @@ -1746,6 +2513,7 @@ pub struct PaypalCaptureResponse { amount: Option, invoice_id: Option, final_capture: bool, + payment_source: Option, } impl From for storage_enums::AttemptStatus { @@ -1809,13 +2577,14 @@ impl TryFrom> resource_id: types::ResponseId::ConnectorTransactionId( item.data.request.connector_transaction_id.clone(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: Some(serde_json::json!(PaypalMeta { authorize_id: connector_payment_id.authorize_id, capture_id: Some(item.response.id.clone()), psync_flow: PaypalPaymentIntent::Capture, next_action: None, + order_id: None, })), network_txn_id: None, connector_response_reference_id: item @@ -1866,8 +2635,8 @@ impl status, response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: item @@ -2222,7 +2991,6 @@ pub struct PaypalSourceVerificationRequest { } #[derive(Deserialize, Serialize, Debug)] - pub struct PaypalSourceVerificationResponse { pub verification_status: PaypalSourceVerificationStatus, } @@ -2298,6 +3066,7 @@ impl TryFrom<(PaypalRedirectsWebhooks, PaypalWebhookEventType)> for PaypalOrders intent: webhook_body.intent, status: PaypalOrderStatus::try_from(webhook_event)?, purchase_units: webhook_body.purchase_units, + payment_source: None, }) } } diff --git a/crates/router/src/connector/plaid.rs b/crates/router/src/connector/plaid.rs index 5bfd42994262..f24d7bd8d6d5 100644 --- a/crates/router/src/connector/plaid.rs +++ b/crates/router/src/connector/plaid.rs @@ -13,7 +13,7 @@ use crate::{ services::{ self, request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, + ConnectorIntegration, ConnectorSpecifications, ConnectorValidation, }, types::{ self, @@ -456,3 +456,5 @@ impl api::IncomingWebhook for Plaid { Err((errors::ConnectorError::WebhooksNotImplemented).into()) } } + +impl ConnectorSpecifications for Plaid {} diff --git a/crates/router/src/connector/plaid/transformers.rs b/crates/router/src/connector/plaid/transformers.rs index c4eb5a7a0359..29d4db1297f8 100644 --- a/crates/router/src/connector/plaid/transformers.rs +++ b/crates/router/src/connector/plaid/transformers.rs @@ -163,21 +163,15 @@ impl TryFrom<&types::PaymentsPostProcessingRouterData> for PlaidLinkTokenRequest match item.request.payment_method_data { domain::PaymentMethodData::OpenBanking(ref data) => match data { domain::OpenBankingData::OpenBankingPIS { .. } => { - let headers = item.header_payload.clone().ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "header_payload", - }, - )?; + let headers = item.header_payload.clone(); - let platform = headers.x_client_platform.ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "x_client_platform", - }, - )?; + let platform = headers + .as_ref() + .and_then(|headers| headers.x_client_platform.clone()); let (is_android, is_ios) = match platform { - common_enums::ClientPlatform::Android => (true, false), - common_enums::ClientPlatform::Ios => (false, true), + Some(common_enums::ClientPlatform::Android) => (true, false), + Some(common_enums::ClientPlatform::Ios) => (false, true), _ => (false, false), }; @@ -208,20 +202,16 @@ impl TryFrom<&types::PaymentsPostProcessingRouterData> for PlaidLinkTokenRequest .ok_or(errors::ConnectorError::MissingConnectorTransactionID)?, }, android_package_name: if is_android { - Some(headers.x_app_id.ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "x-app-id", - }, - )?) + headers + .as_ref() + .and_then(|headers| headers.x_app_id.clone()) } else { None }, redirect_uri: if is_ios { - Some(headers.x_redirect_uri.ok_or( - errors::ConnectorError::MissingRequiredField { - field_name: "x_redirect_uri", - }, - )?) + headers + .as_ref() + .and_then(|headers| headers.x_redirect_uri.clone()) } else { None }, @@ -318,8 +308,8 @@ impl resource_id: types::ResponseId::ConnectorTransactionId( item.response.payment_id.clone(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(item.response.payment_id), @@ -404,8 +394,8 @@ impl TryFrom + Sync), +} impl Riskified { + pub fn new() -> &'static Self { + &Self { + amount_converter: &StringMajorUnitForConnector, + } + } + #[cfg(feature = "frm")] pub fn generate_authorization_signature( &self, @@ -173,7 +184,17 @@ impl req: &frm_types::FrmCheckoutRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - let req_obj = riskified::RiskifiedPaymentsCheckoutRequest::try_from(req)?; + let amount = convert_amount( + self.amount_converter, + MinorUnit::new(req.request.amount), + req.request + .currency + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "currency", + })?, + )?; + let req_data = riskified::RiskifiedRouterData::from((amount, req)); + let req_obj = riskified::RiskifiedPaymentsCheckoutRequest::try_from(&req_data)?; Ok(RequestContent::Json(Box::new(req_obj))) } @@ -293,7 +314,17 @@ impl Ok(RequestContent::Json(Box::new(req_obj))) } _ => { - let req_obj = riskified::TransactionSuccessRequest::try_from(req)?; + let amount = convert_amount( + self.amount_converter, + MinorUnit::new(req.request.amount), + req.request + .currency + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "currency", + })?, + )?; + let req_data = riskified::RiskifiedRouterData::from((amount, req)); + let req_obj = riskified::TransactionSuccessRequest::try_from(&req_data)?; Ok(RequestContent::Json(Box::new(req_obj))) } } @@ -646,3 +677,5 @@ impl api::IncomingWebhook for Riskified { Ok(Box::new(resource)) } } + +impl ConnectorSpecifications for Riskified {} diff --git a/crates/router/src/connector/riskified/transformers/api.rs b/crates/router/src/connector/riskified/transformers/api.rs index 2e0ac3b00473..e8c1b8744179 100644 --- a/crates/router/src/connector/riskified/transformers/api.rs +++ b/crates/router/src/connector/riskified/transformers/api.rs @@ -1,5 +1,10 @@ use api_models::payments::AdditionalPaymentData; -use common_utils::{ext_traits::ValueExt, id_type, pii::Email}; +use common_utils::{ + ext_traits::ValueExt, + id_type, + pii::Email, + types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector}, +}; use error_stack::{self, ResultExt}; use masking::Secret; use serde::{Deserialize, Serialize}; @@ -7,17 +12,37 @@ use time::PrimitiveDateTime; use crate::{ connector::utils::{ - AddressDetailsData, FraudCheckCheckoutRequest, FraudCheckTransactionRequest, RouterData, + convert_amount, AddressDetailsData, FraudCheckCheckoutRequest, + FraudCheckTransactionRequest, RouterData, }, core::{errors, fraud_check::types as core_types}, types::{ - self, api, api::Fulfillment, fraud_check as frm_types, storage::enums as storage_enums, + self, + api::{self, Fulfillment}, + fraud_check as frm_types, + storage::enums as storage_enums, ResponseId, ResponseRouterData, }, }; type Error = error_stack::Report; +pub struct RiskifiedRouterData { + pub amount: StringMajorUnit, + pub router_data: T, + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl From<(StringMajorUnit, T)> for RiskifiedRouterData { + fn from((amount, router_data): (StringMajorUnit, T)) -> Self { + Self { + amount, + router_data, + amount_converter: &StringMajorUnitForConnector, + } + } +} + #[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] pub struct RiskifiedPaymentsCheckoutRequest { order: CheckoutRequest, @@ -35,7 +60,7 @@ pub struct CheckoutRequest { updated_at: PrimitiveDateTime, gateway: Option, browser_ip: Option, - total_price: i64, + total_price: StringMajorUnit, total_discounts: i64, cart_token: String, referring_site: String, @@ -60,13 +85,13 @@ pub struct PaymentDetails { #[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] pub struct ShippingLines { - price: i64, + price: StringMajorUnit, title: Option, } #[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] pub struct DiscountCodes { - amount: i64, + amount: StringMajorUnit, code: Option, } @@ -110,10 +135,10 @@ pub struct OrderAddress { #[derive(Debug, Deserialize, Serialize, Eq, PartialEq, Clone)] pub struct LineItem { - price: i64, + price: StringMajorUnit, quantity: i32, title: String, - product_type: Option, + product_type: Option, requires_shipping: Option, product_id: Option, category: Option, @@ -132,9 +157,14 @@ pub struct RiskifiedMetadata { shipping_lines: Vec, } -impl TryFrom<&frm_types::FrmCheckoutRouterData> for RiskifiedPaymentsCheckoutRequest { +impl TryFrom<&RiskifiedRouterData<&frm_types::FrmCheckoutRouterData>> + for RiskifiedPaymentsCheckoutRequest +{ type Error = Error; - fn try_from(payment_data: &frm_types::FrmCheckoutRouterData) -> Result { + fn try_from( + payment: &RiskifiedRouterData<&frm_types::FrmCheckoutRouterData>, + ) -> Result { + let payment_data = payment.router_data.clone(); let metadata: RiskifiedMetadata = payment_data .frm_metadata .clone() @@ -148,6 +178,33 @@ impl TryFrom<&frm_types::FrmCheckoutRouterData> for RiskifiedPaymentsCheckoutReq 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()?; + let line_items = payment_data + .request + .get_order_details()? + .iter() + .map(|order_detail| { + let price = convert_amount( + payment.amount_converter, + order_detail.amount, + payment_data.request.currency.ok_or_else(|| { + errors::ConnectorError::MissingRequiredField { + field_name: "currency", + } + })?, + )?; + + Ok(LineItem { + price, + quantity: i32::from(order_detail.quantity), + title: order_detail.product_name.clone(), + product_type: order_detail.product_type.clone(), + requires_shipping: order_detail.requires_shipping, + product_id: order_detail.product_id.clone(), + category: order_detail.category.clone(), + brand: order_detail.brand.clone(), + }) + }) + .collect::, Self::Error>>()?; Ok(Self { order: CheckoutRequest { @@ -156,23 +213,9 @@ impl TryFrom<&frm_types::FrmCheckoutRouterData> for RiskifiedPaymentsCheckoutReq created_at: common_utils::date_time::now(), updated_at: common_utils::date_time::now(), gateway: payment_data.request.gateway.clone(), - total_price: payment_data.request.amount, + total_price: payment.amount.clone(), cart_token: payment_data.attempt_id.clone(), - line_items: payment_data - .request - .get_order_details()? - .iter() - .map(|order_detail| LineItem { - price: order_detail.amount, - quantity: i32::from(order_detail.quantity), - title: order_detail.product_name.clone(), - product_type: order_detail.product_type.clone(), - requires_shipping: order_detail.requires_shipping, - product_id: order_detail.product_id.clone(), - category: order_detail.category.clone(), - brand: order_detail.brand.clone(), - }) - .collect::>(), + line_items, source: Source::DesktopWeb, billing_address: OrderAddress::try_from(billing_address).ok(), shipping_address: OrderAddress::try_from(shipping_address).ok(), @@ -411,7 +454,7 @@ pub struct SuccessfulTransactionData { pub struct TransactionDecisionData { external_status: TransactionStatus, reason: Option, - amount: i64, + amount: StringMajorUnit, currency: storage_enums::Currency, #[serde(with = "common_utils::custom_serde::iso8601")] decided_at: PrimitiveDateTime, @@ -429,16 +472,21 @@ pub enum TransactionStatus { Approved, } -impl TryFrom<&frm_types::FrmTransactionRouterData> for TransactionSuccessRequest { +impl TryFrom<&RiskifiedRouterData<&frm_types::FrmTransactionRouterData>> + for TransactionSuccessRequest +{ type Error = Error; - fn try_from(item: &frm_types::FrmTransactionRouterData) -> Result { + fn try_from( + item_data: &RiskifiedRouterData<&frm_types::FrmTransactionRouterData>, + ) -> Result { + let item = item_data.router_data.clone(); Ok(Self { order: SuccessfulTransactionData { id: item.attempt_id.clone(), decision: TransactionDecisionData { external_status: TransactionStatus::Approved, reason: None, - amount: item.request.amount, + amount: item_data.amount.clone(), currency: item.request.get_currency()?, decided_at: common_utils::date_time::now(), payment_details: [TransactionPaymentDetails { @@ -572,9 +620,11 @@ pub struct ErrorData { pub message: String, } -impl TryFrom<&api_models::payments::Address> for OrderAddress { +impl TryFrom<&hyperswitch_domain_models::address::Address> for OrderAddress { type Error = Error; - fn try_from(address_info: &api_models::payments::Address) -> Result { + fn try_from( + address_info: &hyperswitch_domain_models::address::Address, + ) -> Result { let address = address_info .clone() @@ -609,7 +659,6 @@ fn get_fulfillment_status( } #[derive(Debug, Clone, Deserialize, Serialize)] - pub struct RiskifiedWebhookBody { pub id: String, pub status: RiskifiedWebhookStatus, diff --git a/crates/router/src/connector/signifyd.rs b/crates/router/src/connector/signifyd.rs index 4ee36d8966ba..21452819c39a 100644 --- a/crates/router/src/connector/signifyd.rs +++ b/crates/router/src/connector/signifyd.rs @@ -20,7 +20,7 @@ use crate::{ configs::settings, core::errors::{self, CustomResult}, headers, - services::{self, request, ConnectorIntegration, ConnectorValidation}, + services::{self, request, ConnectorIntegration, ConnectorSpecifications, ConnectorValidation}, types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, @@ -754,3 +754,5 @@ impl api::IncomingWebhook for Signifyd { Ok(Box::new(resource)) } } + +impl ConnectorSpecifications for Signifyd {} diff --git a/crates/router/src/connector/signifyd/transformers/api.rs b/crates/router/src/connector/signifyd/transformers/api.rs index 6aeda8f8d470..028c0e825189 100644 --- a/crates/router/src/connector/signifyd/transformers/api.rs +++ b/crates/router/src/connector/signifyd/transformers/api.rs @@ -145,7 +145,7 @@ impl TryFrom<&frm_types::FrmSaleRouterData> for SignifydPaymentsSaleRequest { .iter() .map(|order_detail| Products { item_name: order_detail.product_name.clone(), - item_price: order_detail.amount, + item_price: order_detail.amount.get_amount_as_i64(), // This should be changed to MinorUnit when we implement amount conversion for this connector. Additionally, the function get_amount_as_i64() should be avoided in the future. item_quantity: i32::from(order_detail.quantity), item_id: order_detail.product_id.clone(), item_category: order_detail.category.clone(), @@ -153,7 +153,7 @@ impl TryFrom<&frm_types::FrmSaleRouterData> for SignifydPaymentsSaleRequest { item_is_digital: order_detail .product_type .as_ref() - .map(|product| (product == &api_models::payments::ProductType::Digital)), + .map(|product| (product == &common_enums::ProductType::Digital)), }) .collect::>(); let metadata: SignifydFrmMetadata = item @@ -382,7 +382,7 @@ impl TryFrom<&frm_types::FrmCheckoutRouterData> for SignifydPaymentsCheckoutRequ .iter() .map(|order_detail| Products { item_name: order_detail.product_name.clone(), - item_price: order_detail.amount, + item_price: order_detail.amount.get_amount_as_i64(), // This should be changed to MinorUnit when we implement amount conversion for this connector. Additionally, the function get_amount_as_i64() should be avoided in the future. item_quantity: i32::from(order_detail.quantity), item_id: order_detail.product_id.clone(), item_category: order_detail.category.clone(), @@ -390,7 +390,7 @@ impl TryFrom<&frm_types::FrmCheckoutRouterData> for SignifydPaymentsCheckoutRequ item_is_digital: order_detail .product_type .as_ref() - .map(|product| (product == &api_models::payments::ProductType::Digital)), + .map(|product| (product == &common_enums::ProductType::Digital)), }) .collect::>(); let metadata: SignifydFrmMetadata = item @@ -702,7 +702,6 @@ 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, diff --git a/crates/router/src/connector/stripe.rs b/crates/router/src/connector/stripe.rs index 1125c0af6dc8..6d377171121c 100644 --- a/crates/router/src/connector/stripe.rs +++ b/crates/router/src/connector/stripe.rs @@ -8,6 +8,7 @@ use common_utils::{ }; use diesel_models::enums; use error_stack::ResultExt; +use hyperswitch_domain_models::router_request_types::SplitRefundsRequest; use masking::PeekInterface; use router_env::{instrument, tracing}; use stripe::auth_headers; @@ -29,7 +30,7 @@ use crate::{ services::{ self, request::{self, Mask}, - ConnectorValidation, + ConnectorSpecifications, ConnectorValidation, }, types::{ self, @@ -133,14 +134,17 @@ impl ConnectorCommon for Stripe { } impl ConnectorValidation for Stripe { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::SequentialAutomatic + | enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( connector_utils::construct_not_supported_error_report(capture_method, self.id()), ), @@ -761,13 +765,20 @@ impl )]; let mut api_key = self.get_auth_header(&req.connector_auth_type)?; header.append(&mut api_key); - req.request.charges.as_ref().map(|charges| { - transformers::transform_headers_for_connect_platform( - charges.charge_type.clone(), - charges.transfer_account_id.clone(), - &mut header, - ) - }); + + if let Some(split_payments) = &req.request.split_payments { + match split_payments { + common_types::payments::SplitPaymentsRequest::StripeSplitPayment( + stripe_split_payment, + ) => { + transformers::transform_headers_for_connect_platform( + stripe_split_payment.charge_type.clone(), + stripe_split_payment.transfer_account_id.clone(), + &mut header, + ); + } + } + } Ok(header) } @@ -932,20 +943,28 @@ 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 { + if let Some(split_payments) = &req.request.split_payments { + match split_payments { + common_types::payments::SplitPaymentsRequest::StripeSplitPayment( + stripe_split_payment, + ) => { + if stripe_split_payment.charge_type + == api::enums::PaymentChargeType::Stripe( + api::enums::StripeChargeType::Direct, + ) + { let mut customer_account_header = vec![( headers::STRIPE_COMPATIBLE_CONNECT_ACCOUNT.to_string(), - charge.transfer_account_id.clone().into_masked(), + stripe_split_payment + .transfer_account_id + .clone() + .into_masked(), )]; header.append(&mut customer_account_header); } } - }); + } + } Ok(header) } @@ -1465,20 +1484,26 @@ 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); + if let Some(split_refunds) = req.request.split_refunds.as_ref() { + match split_refunds { + SplitRefundsRequest::StripeSplitRefund(ref stripe_split_refund) => { + match &stripe_split_refund.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(), + stripe_split_refund + .transfer_account_id + .clone() + .into_masked(), + )]; + header.append(&mut customer_account_header); + } + } } } - }); + } + } Ok(header) } @@ -1504,14 +1529,16 @@ impl services::ConnectorIntegration RequestContent::FormUrlEncoded(Box::new(stripe::RefundRequest::try_from(( req, refund_amount, ))?)), - Some(_) => RequestContent::FormUrlEncoded(Box::new( - stripe::ChargeRefundRequest::try_from(req)?, - )), + Some(split_refunds) => match split_refunds { + SplitRefundsRequest::StripeSplitRefund(_) => RequestContent::FormUrlEncoded( + Box::new(stripe::ChargeRefundRequest::try_from(req)?), + ), + }, }; Ok(request_body) } @@ -1625,13 +1652,18 @@ impl services::ConnectorIntegration { + transformers::transform_headers_for_connect_platform( + stripe_refund.charge_type.clone(), + stripe_refund.transfer_account_id.clone(), + &mut header, + ); + } + } + } Ok(header) } @@ -2901,3 +2933,5 @@ impl self.build_error_response(res, event_builder) } } + +impl ConnectorSpecifications for Stripe {} diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index a81fecba9cf7..48736c00e7d8 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -72,7 +72,9 @@ impl From> for StripeCaptureMethod { Some(p) => match p { enums::CaptureMethod::ManualMultiple => Self::Manual, enums::CaptureMethod::Manual => Self::Manual, - enums::CaptureMethod::Automatic => Self::Automatic, + enums::CaptureMethod::Automatic | enums::CaptureMethod::SequentialAutomatic => { + Self::Automatic + } enums::CaptureMethod::Scheduled => Self::Manual, }, None => Self::Automatic, @@ -88,6 +90,14 @@ pub enum Auth3ds { Any, } +#[derive(Debug, Eq, PartialEq, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum StripeCardNetwork { + CartesBancaires, + Mastercard, + Visa, +} + #[derive(Debug, Eq, PartialEq, Serialize)] #[serde( rename_all = "snake_case", @@ -220,6 +230,8 @@ pub struct StripeCardData { pub payment_method_data_card_cvc: Option>, #[serde(rename = "payment_method_options[card][request_three_d_secure]")] pub payment_method_auth_type: Option, + #[serde(rename = "payment_method_options[card][network]")] + pub payment_method_data_card_preferred_network: Option, } #[derive(Debug, Eq, PartialEq, Serialize)] pub struct StripePayLaterData { @@ -687,6 +699,7 @@ impl TryFrom for StripePaymentMethodType { | enums::PaymentMethodType::Alma | enums::PaymentMethodType::ClassicReward | enums::PaymentMethodType::Dana + | enums::PaymentMethodType::DirectCarrierBilling | enums::PaymentMethodType::Efecty | enums::PaymentMethodType::Evoucher | enums::PaymentMethodType::GoPay @@ -727,6 +740,7 @@ impl TryFrom for StripePaymentMethodType { | enums::PaymentMethodType::MandiriVa | enums::PaymentMethodType::PermataBankTransfer | enums::PaymentMethodType::PaySafeCard + | enums::PaymentMethodType::Paze | enums::PaymentMethodType::Givex | enums::PaymentMethodType::Benefit | enums::PaymentMethodType::Knet @@ -978,6 +992,7 @@ impl TryFrom<&domain::payments::PayLaterData> for StripePaymentMethodType { } domain::PayLaterData::KlarnaSdk { .. } + | domain::PayLaterData::KlarnaCheckout {} | domain::PayLaterData::PayBrightRedirect {} | domain::PayLaterData::WalleyRedirect {} | domain::PayLaterData::AlmaRedirect {} @@ -1052,6 +1067,7 @@ impl ForeignTryFrom<&domain::WalletData> for Option { | domain::WalletData::GooglePayThirdPartySdk(_) | domain::WalletData::MbWayRedirect(_) | domain::WalletData::PaypalSdk(_) + | domain::WalletData::Paze(_) | domain::WalletData::SamsungPay(_) | domain::WalletData::TwintRedirect {} | domain::WalletData::VippsRedirect {} @@ -1332,10 +1348,12 @@ fn create_stripe_payment_method( domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::MandatePayment | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( connector_util::get_unimplemented_payment_method_error_message("stripe"), ) @@ -1344,6 +1362,22 @@ fn create_stripe_payment_method( } } +fn get_stripe_card_network(card_network: common_enums::CardNetwork) -> Option { + match card_network { + common_enums::CardNetwork::Visa => Some(StripeCardNetwork::Visa), + common_enums::CardNetwork::Mastercard => Some(StripeCardNetwork::Mastercard), + common_enums::CardNetwork::CartesBancaires => Some(StripeCardNetwork::CartesBancaires), + common_enums::CardNetwork::AmericanExpress + | common_enums::CardNetwork::JCB + | common_enums::CardNetwork::DinersClub + | common_enums::CardNetwork::Discover + | common_enums::CardNetwork::UnionPay + | common_enums::CardNetwork::Interac + | common_enums::CardNetwork::RuPay + | common_enums::CardNetwork::Maestro => None, + } +} + impl TryFrom<(&domain::Card, Auth3ds)> for StripePaymentMethodData { type Error = errors::ConnectorError; fn try_from( @@ -1356,6 +1390,10 @@ impl TryFrom<(&domain::Card, Auth3ds)> for StripePaymentMethodData { payment_method_data_card_exp_year: card.card_exp_year.clone(), payment_method_data_card_cvc: Some(card.card_cvc.clone()), payment_method_auth_type: Some(payment_method_auth_type), + payment_method_data_card_preferred_network: card + .card_network + .clone() + .and_then(get_stripe_card_network), })) } } @@ -1448,6 +1486,7 @@ impl TryFrom<(&domain::WalletData, Option)> for Strip | domain::WalletData::GooglePayThirdPartySdk(_) | domain::WalletData::MbWayRedirect(_) | domain::WalletData::PaypalSdk(_) + | domain::WalletData::Paze(_) | domain::WalletData::SamsungPay(_) | domain::WalletData::TwintRedirect {} | domain::WalletData::VippsRedirect {} @@ -1664,7 +1703,13 @@ impl TryFrom<(&types::PaymentsAuthorizeRouterData, MinorUnit)> for PaymentIntent }; let mut payment_method_options = None; - let (mut payment_data, payment_method, billing_address, payment_method_types) = { + let ( + mut payment_data, + payment_method, + billing_address, + payment_method_types, + setup_future_usage, + ) = { match item .request .mandate_id @@ -1675,9 +1720,10 @@ impl TryFrom<(&types::PaymentsAuthorizeRouterData, MinorUnit)> for PaymentIntent connector_mandate_ids, )) => ( None, - connector_mandate_ids.connector_mandate_id, + connector_mandate_ids.get_connector_mandate_id(), StripeBillingAddress::default(), get_payment_method_type_for_saved_payment_method_payment(item)?, + None, ), Some(api_models::payments::MandateReferenceId::NetworkMandateId( network_transaction_id, @@ -1691,16 +1737,28 @@ impl TryFrom<(&types::PaymentsAuthorizeRouterData, MinorUnit)> for PaymentIntent }); let payment_data = match item.request.payment_method_data { - domain::payments::PaymentMethodData::Card(ref card) => { - StripePaymentMethodData::Card(StripeCardData { - payment_method_data_type: StripePaymentMethodType::Card, - payment_method_data_card_number: card.card_number.clone(), - payment_method_data_card_exp_month: card.card_exp_month.clone(), - payment_method_data_card_exp_year: card.card_exp_year.clone(), - payment_method_data_card_cvc: None, - payment_method_auth_type: None, - }) - } + domain::payments::PaymentMethodData::CardDetailsForNetworkTransactionId( + ref card_details_for_network_transaction_id, + ) => StripePaymentMethodData::Card(StripeCardData { + payment_method_data_type: StripePaymentMethodType::Card, + payment_method_data_card_number: + card_details_for_network_transaction_id.card_number.clone(), + payment_method_data_card_exp_month: + card_details_for_network_transaction_id + .card_exp_month + .clone(), + payment_method_data_card_exp_year: + card_details_for_network_transaction_id + .card_exp_year + .clone(), + payment_method_data_card_cvc: None, + payment_method_auth_type: None, + payment_method_data_card_preferred_network: + card_details_for_network_transaction_id + .card_network + .clone() + .and_then(get_stripe_card_network), + }), domain::payments::PaymentMethodData::CardRedirect(_) | domain::payments::PaymentMethodData::Wallet(_) | domain::payments::PaymentMethodData::PayLater(_) @@ -1711,12 +1769,14 @@ impl TryFrom<(&types::PaymentsAuthorizeRouterData, MinorUnit)> for PaymentIntent | domain::payments::PaymentMethodData::MandatePayment | domain::payments::PaymentMethodData::Reward | domain::payments::PaymentMethodData::RealTimePayment(_) + | domain::payments::PaymentMethodData::MobilePayment(_) | domain::payments::PaymentMethodData::Upi(_) | domain::payments::PaymentMethodData::Voucher(_) | domain::payments::PaymentMethodData::GiftCard(_) | domain::payments::PaymentMethodData::OpenBanking(_) | domain::payments::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::Card(_) => { Err(errors::ConnectorError::NotSupported { message: "Network tokenization for payment method".to_string(), connector: "Stripe", @@ -1729,9 +1789,10 @@ impl TryFrom<(&types::PaymentsAuthorizeRouterData, MinorUnit)> for PaymentIntent None, StripeBillingAddress::default(), None, + None, ) } - _ => { + Some(api_models::payments::MandateReferenceId::NetworkTokenWithNTI(_)) | None => { let (payment_method_data, payment_method_type, billing_address) = create_stripe_payment_method( &item.request.payment_method_data, @@ -1753,6 +1814,7 @@ impl TryFrom<(&types::PaymentsAuthorizeRouterData, MinorUnit)> for PaymentIntent None, billing_address, payment_method_type, + item.request.setup_future_usage, ) } } @@ -1775,6 +1837,9 @@ impl TryFrom<(&types::PaymentsAuthorizeRouterData, MinorUnit)> for PaymentIntent wallet_name: "Apple Pay".to_string(), })? } + types::PaymentMethodToken::PazeDecrypt(_) => { + Err(crate::unimplemented_payment_method!("Paze", "Stripe"))? + } }; Some(StripePaymentMethodData::Wallet( StripeWallet::ApplepayPayment(ApplepayPayment { @@ -1864,19 +1929,27 @@ impl TryFrom<(&types::PaymentsAuthorizeRouterData, MinorUnit)> for PaymentIntent 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()), - }), - }, + let (charges, customer) = match &item.request.split_payments { + Some(common_types::payments::SplitPaymentsRequest::StripeSplitPayment( + stripe_split_payment, + )) => { + let charges = match &stripe_split_payment.charge_type { + api_models::enums::PaymentChargeType::Stripe(charge_type) => { + match charge_type { + api_models::enums::StripeChargeType::Direct => Some(IntentCharges { + application_fee_amount: stripe_split_payment.application_fees, + destination_account_id: None, + }), + api_models::enums::StripeChargeType::Destination => { + Some(IntentCharges { + application_fee_amount: stripe_split_payment.application_fees, + destination_account_id: Some( + stripe_split_payment.transfer_account_id.clone(), + ), + }) + } + } + } }; (charges, None) } @@ -1905,7 +1978,7 @@ impl TryFrom<(&types::PaymentsAuthorizeRouterData, MinorUnit)> for PaymentIntent customer, setup_mandate_details, off_session: item.request.off_session, - setup_future_usage: item.request.setup_future_usage, + setup_future_usage, payment_method_types, expand: Some(ExpandableObjects::LatestCharge), browser_info, @@ -2174,6 +2247,12 @@ pub struct StripeBankRedirectDetails { attached_payment_method: Option>, } +#[derive(Deserialize, Clone, Debug, PartialEq, Eq, Serialize)] +pub struct StripeCashappDetails { + buyer_id: Option, + cashtag: Option, +} + impl Deref for PaymentIntentSyncResponse { type Target = PaymentIntentResponse; @@ -2213,6 +2292,9 @@ pub enum StripePaymentMethodDetailsResponse { Card { card: StripeAdditionalCardDetails, }, + Cashapp { + cashapp: StripeCashappDetails, + }, Klarna, Affirm, AfterpayClearpay, @@ -2270,7 +2352,8 @@ impl StripePaymentMethodDetailsResponse { | Self::Bacs | Self::Wechatpay | Self::Alipay - | Self::CustomerBalance => None, + | Self::CustomerBalance + | Self::Cashapp { .. } => None, } } } @@ -2390,6 +2473,7 @@ impl connector_mandate_id, payment_method_id, mandate_metadata: None, + connector_mandate_request_reference_id: None, } }); @@ -2430,8 +2514,8 @@ impl }); Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()), - redirection_data, - mandate_reference, + redirection_data: Box::new(redirection_data), + mandate_reference: Box::new(mandate_reference), connector_metadata, network_txn_id, connector_response_reference_id: Some(item.response.id), @@ -2475,7 +2559,16 @@ pub fn get_connector_metadata( let (sepa_bank_instructions, bacs_bank_instructions) = bank_instructions.map_or((None, None), |financial_address| { ( - financial_address.iban.to_owned(), + financial_address + .iban + .to_owned() + .map(|sepa_financial_details| SepaFinancialDetails { + account_holder_name: sepa_financial_details.account_holder_name, + bic: sepa_financial_details.bic, + country: sepa_financial_details.country, + iban: sepa_financial_details.iban, + reference: response.reference.to_owned(), + }), financial_address.sort_code.to_owned(), ) }); @@ -2576,6 +2669,7 @@ impl | Some(StripePaymentMethodDetailsResponse::Wechatpay) | Some(StripePaymentMethodDetailsResponse::Alipay) | Some(StripePaymentMethodDetailsResponse::CustomerBalance) + | Some(StripePaymentMethodDetailsResponse::Cashapp { .. }) | None => payment_method_id.expose(), } } @@ -2585,6 +2679,7 @@ impl connector_mandate_id, payment_method_id: Some(payment_method_id), mandate_metadata: None, + connector_mandate_request_reference_id: None, } }); @@ -2627,8 +2722,8 @@ impl }); Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()), - redirection_data, - mandate_reference, + redirection_data: Box::new(redirection_data), + mandate_reference: Box::new(mandate_reference), connector_metadata, network_txn_id: network_transaction_id, connector_response_reference_id: Some(item.response.id.clone()), @@ -2676,6 +2771,7 @@ impl connector_mandate_id, payment_method_id, mandate_metadata: None, + connector_mandate_request_reference_id: None, } }); let status = enums::AttemptStatus::from(item.response.status); @@ -2706,8 +2802,8 @@ impl Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()), - redirection_data, - mandate_reference, + redirection_data: Box::new(redirection_data), + mandate_reference: Box::new(mandate_reference), connector_metadata: None, network_txn_id: network_transaction_id, connector_response_reference_id: Some(item.response.id), @@ -2880,6 +2976,7 @@ pub struct SepaFinancialDetails { pub bic: Secret, pub country: Secret, pub iban: Secret, + pub reference: Option, } #[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] @@ -2931,32 +3028,38 @@ impl TryFrom<&types::RefundsRouterData> for ChargeRefundRequest { type Error = error_stack::Report; fn try_from(item: &types::RefundsRouterData) -> Result { let amount = item.request.minor_refund_amount; - match item.request.charges.as_ref() { + match item.request.split_refunds.as_ref() { None => Err(errors::ConnectorError::MissingRequiredField { - field_name: "charges", + field_name: "split_refunds", } .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()), - }, - }) - } + + Some(split_refunds) => match split_refunds { + types::SplitRefundsRequest::StripeSplitRefund(stripe_refund) => { + let (refund_application_fee, reverse_transfer) = match &stripe_refund.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: stripe_refund.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()), + }, + }) + } + }, } } } @@ -3199,7 +3302,7 @@ pub struct MitExemption { #[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] #[serde(untagged)] pub enum LatestAttempt { - PaymentIntentAttempt(LatestPaymentAttempt), + PaymentIntentAttempt(Box), SetupAttempt(String), } #[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)] @@ -3259,7 +3362,7 @@ impl email: item.get_billing_email().or(item.request.get_email())?, }, amount: Some(amount), - return_url: Some(item.get_return_url()?), + return_url: Some(item.request.get_router_return_url()?), }), ), domain::BankTransferData::AchBankTransfer { .. } => { @@ -3300,6 +3403,7 @@ impl | Some(domain::PaymentMethodData::Crypto(..)) | Some(domain::PaymentMethodData::Reward) | Some(domain::PaymentMethodData::RealTimePayment(..)) + | Some(domain::PaymentMethodData::MobilePayment(..)) | Some(domain::PaymentMethodData::MandatePayment) | Some(domain::PaymentMethodData::Upi(..)) | Some(domain::PaymentMethodData::GiftCard(..)) @@ -3308,6 +3412,7 @@ impl | Some(domain::PaymentMethodData::OpenBanking(..)) | Some(domain::PaymentMethodData::CardToken(..)) | Some(domain::PaymentMethodData::NetworkToken(..)) + | Some(domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_)) | None => Err(errors::ConnectorError::NotImplemented( connector_util::get_unimplemented_payment_method_error_message("stripe"), ) @@ -3408,8 +3513,8 @@ impl TryFrom, - pub currency: String, + #[serde(default, deserialize_with = "connector_util::convert_uppercase")] + pub currency: enums::Currency, pub payment_intent: Option, pub client_secret: Option>, pub reason: Option, @@ -3756,13 +3862,15 @@ impl | domain::PaymentMethodData::Crypto(_) | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::CardRedirect(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( connector_util::get_unimplemented_payment_method_error_message("stripe"), ))? diff --git a/crates/router/src/connector/threedsecureio.rs b/crates/router/src/connector/threedsecureio.rs index a051cf471f19..aec8f7804512 100644 --- a/crates/router/src/connector/threedsecureio.rs +++ b/crates/router/src/connector/threedsecureio.rs @@ -15,7 +15,7 @@ use crate::{ services::{ self, request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, + ConnectorIntegration, ConnectorSpecifications, ConnectorValidation, }, types::{ self, @@ -530,3 +530,5 @@ impl > for Threedsecureio { } + +impl ConnectorSpecifications for Threedsecureio {} diff --git a/crates/router/src/connector/trustpay.rs b/crates/router/src/connector/trustpay.rs index deb7ec796181..10c71a5d3754 100644 --- a/crates/router/src/connector/trustpay.rs +++ b/crates/router/src/connector/trustpay.rs @@ -29,7 +29,7 @@ use crate::{ services::{ self, request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, + ConnectorIntegration, ConnectorSpecifications, ConnectorValidation, }, types::{ self, @@ -688,7 +688,7 @@ impl ConnectorIntegration Ok(format!("{}{}", self.base_url(connectors), "api/v1/Reverse")), + _ => Ok(format!("{}{}", self.base_url(connectors), "api/v1/Refund")), } } @@ -1077,3 +1077,5 @@ impl ConnectorErrorTypeMapping for Trustpay { } } } + +impl ConnectorSpecifications for Trustpay {} diff --git a/crates/router/src/connector/trustpay/transformers.rs b/crates/router/src/connector/trustpay/transformers.rs index d05db4d28ab5..83587609b3ff 100644 --- a/crates/router/src/connector/trustpay/transformers.rs +++ b/crates/router/src/connector/trustpay/transformers.rs @@ -350,7 +350,7 @@ fn get_bank_redirection_request_data( auth: TrustpayAuthType, ) -> Result> { let pm = TrustpayPaymentMethod::try_from(bank_redirection_data)?; - let return_url = item.request.get_return_url()?; + let return_url = item.request.get_router_return_url()?; let payment_request = TrustpayPaymentsRequest::BankRedirectPaymentRequest(Box::new(PaymentRequestBankRedirect { payment_method: pm.clone(), @@ -398,6 +398,9 @@ impl TryFrom<&TrustpayRouterData<&types::PaymentsAuthorizeRouterData>> for Trust accept_header: Some(browser_info.accept_header.unwrap_or("*".to_string())), user_agent: browser_info.user_agent, ip_address: browser_info.ip_address, + os_type: None, + os_version: None, + device_model: None, }; let params = get_mandatory_fields(item.router_data)?; let amount = item.amount.to_owned(); @@ -410,7 +413,7 @@ impl TryFrom<&TrustpayRouterData<&types::PaymentsAuthorizeRouterData>> for Trust params, amount, ccard, - item.router_data.request.get_return_url()?, + item.router_data.request.get_router_return_url()?, )?), domain::PaymentMethodData::BankRedirect(ref bank_redirection_data) => { get_bank_redirection_request_data( @@ -430,12 +433,14 @@ impl TryFrom<&TrustpayRouterData<&types::PaymentsAuthorizeRouterData>> for Trust | domain::PaymentMethodData::MandatePayment | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("trustpay"), ) @@ -726,8 +731,8 @@ fn handle_cards_response( }; let payment_response_data = types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(response.instance_id.clone()), - redirection_data, - mandate_reference: None, + redirection_data: Box::new(redirection_data), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -753,11 +758,11 @@ fn handle_bank_redirects_response( resource_id: types::ResponseId::ConnectorTransactionId( response.payment_request_id.to_string(), ), - redirection_data: Some(services::RedirectForm::from(( + redirection_data: Box::new(Some(services::RedirectForm::from(( response.gateway_url, services::Method::Get, - ))), - mandate_reference: None, + )))), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -790,8 +795,8 @@ fn handle_bank_redirects_error_response( }); let payment_response_data = types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::NoResponseId, - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -851,8 +856,8 @@ fn handle_bank_redirects_sync_response( .payment_request_id .clone(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -899,8 +904,8 @@ pub fn handle_webhook_response( }; let payment_response_data = types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::NoResponseId, - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -1229,6 +1234,7 @@ pub fn get_apple_pay_session( merchant_identifier: None, required_billing_contact_fields: None, required_shipping_contact_fields: None, + recurring_payment_request: None, }), connector: "trustpay".to_string(), delayed_session_token: true, @@ -1788,7 +1794,7 @@ pub struct WebhookReferences { #[serde(rename_all = "PascalCase")] pub struct WebhookAmount { pub amount: f64, - pub currency: String, + pub currency: enums::Currency, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index 4da93e2cf017..85b1dd221f72 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -7,7 +7,7 @@ use std::{ use api_models::payouts::{self, PayoutVendorAccountDetails}; use api_models::{ enums::{CanadaStatesAbbreviation, UsStatesAbbreviation}, - payments::{self, OrderDetailsWithAmount}, + payments, }; use base64::Engine; use common_utils::{ @@ -18,7 +18,7 @@ use common_utils::{ pii::{self, Email, IpAddress}, types::{AmountConvertor, MinorUnit}, }; -use diesel_models::enums; +use diesel_models::{enums, types::OrderDetailsWithAmount}; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ mandates, @@ -28,7 +28,7 @@ use hyperswitch_domain_models::{ SyncIntegrityObject, }, }; -use masking::{ExposeInterface, Secret}; +use masking::{Deserialize, ExposeInterface, Secret}; use once_cell::sync::Lazy; use regex::Regex; use serde::Serializer; @@ -54,7 +54,7 @@ use crate::{ pub fn missing_field_err( message: &'static str, -) -> Box error_stack::Report + '_> { +) -> Box error_stack::Report + 'static> { Box::new(move || { errors::ConnectorError::MissingRequiredField { field_name: message, @@ -79,14 +79,20 @@ impl AccessTokenRequestInfo for types::RefreshTokenRouterData { } pub trait RouterData { - fn get_billing(&self) -> Result<&api::Address, Error>; + fn get_billing(&self) -> Result<&hyperswitch_domain_models::address::Address, Error>; fn get_billing_country(&self) -> Result; - fn get_billing_phone(&self) -> Result<&api::PhoneDetails, Error>; + fn get_billing_phone(&self) + -> Result<&hyperswitch_domain_models::address::PhoneDetails, Error>; fn get_description(&self) -> Result; - fn get_return_url(&self) -> Result; - fn get_billing_address(&self) -> Result<&api::AddressDetails, Error>; - fn get_shipping_address(&self) -> Result<&api::AddressDetails, Error>; - fn get_shipping_address_with_phone_number(&self) -> Result<&api::Address, Error>; + fn get_billing_address( + &self, + ) -> Result<&hyperswitch_domain_models::address::AddressDetails, Error>; + fn get_shipping_address( + &self, + ) -> Result<&hyperswitch_domain_models::address::AddressDetails, Error>; + fn get_shipping_address_with_phone_number( + &self, + ) -> Result<&hyperswitch_domain_models::address::Address, Error>; fn get_connector_meta(&self) -> Result; fn get_session_token(&self) -> Result; fn get_billing_first_name(&self) -> Result, Error>; @@ -112,8 +118,8 @@ pub trait RouterData { #[cfg(feature = "payouts")] fn get_quote_id(&self) -> Result; - fn get_optional_billing(&self) -> Option<&api::Address>; - fn get_optional_shipping(&self) -> Option<&api::Address>; + fn get_optional_billing(&self) -> Option<&hyperswitch_domain_models::address::Address>; + fn get_optional_shipping(&self) -> Option<&hyperswitch_domain_models::address::Address>; fn get_optional_shipping_line1(&self) -> Option>; fn get_optional_shipping_line2(&self) -> Option>; fn get_optional_shipping_city(&self) -> Option; @@ -191,7 +197,7 @@ pub fn get_unimplemented_payment_method_error_message(connector: &str) -> String } impl RouterData for types::RouterData { - fn get_billing(&self) -> Result<&api::Address, Error> { + fn get_billing(&self) -> Result<&hyperswitch_domain_models::address::Address, Error> { self.address .get_payment_method_billing() .ok_or_else(missing_field_err("billing")) @@ -207,18 +213,20 @@ impl RouterData for types::RouterData Result<&api::PhoneDetails, Error> { + fn get_billing_phone( + &self, + ) -> Result<&hyperswitch_domain_models::address::PhoneDetails, Error> { self.address .get_payment_method_billing() .and_then(|a| a.phone.as_ref()) .ok_or_else(missing_field_err("billing.phone")) } - fn get_optional_billing(&self) -> Option<&api::Address> { + fn get_optional_billing(&self) -> Option<&hyperswitch_domain_models::address::Address> { self.address.get_payment_method_billing() } - fn get_optional_shipping(&self) -> Option<&api::Address> { + fn get_optional_shipping(&self) -> Option<&hyperswitch_domain_models::address::Address> { self.address.get_shipping() } @@ -312,12 +320,9 @@ impl RouterData for types::RouterData Result { - self.return_url - .clone() - .ok_or_else(missing_field_err("return_url")) - } - fn get_billing_address(&self) -> Result<&api::AddressDetails, Error> { + fn get_billing_address( + &self, + ) -> Result<&hyperswitch_domain_models::address::AddressDetails, Error> { self.address .get_payment_method_billing() .as_ref() @@ -534,14 +539,18 @@ impl RouterData for types::RouterData Result<&api::AddressDetails, Error> { + fn get_shipping_address( + &self, + ) -> Result<&hyperswitch_domain_models::address::AddressDetails, Error> { self.address .get_shipping() .and_then(|a| a.address.as_ref()) .ok_or_else(missing_field_err("shipping.address")) } - fn get_shipping_address_with_phone_number(&self) -> Result<&api::Address, Error> { + fn get_shipping_address_with_phone_number( + &self, + ) -> Result<&hyperswitch_domain_models::address::Address, Error> { self.address .get_shipping() .ok_or_else(missing_field_err("shipping")) @@ -602,7 +611,7 @@ pub trait AddressData { fn get_optional_full_name(&self) -> Option>; } -impl AddressData for api::Address { +impl AddressData for hyperswitch_domain_models::address::Address { fn get_email(&self) -> Result { self.email.clone().ok_or_else(missing_field_err("email")) } @@ -629,6 +638,7 @@ impl AddressData for api::Address { } pub trait PaymentsPreProcessingData { + fn get_redirect_response_payload(&self) -> Result; fn get_email(&self) -> Result; fn get_payment_method_type(&self) -> Result; fn get_currency(&self) -> Result; @@ -637,7 +647,7 @@ pub trait PaymentsPreProcessingData { fn is_auto_capture(&self) -> Result; fn get_order_details(&self) -> Result, Error>; fn get_webhook_url(&self) -> Result; - fn get_return_url(&self) -> Result; + fn get_router_return_url(&self) -> Result; fn get_browser_info(&self) -> Result; fn get_complete_authorize_url(&self) -> Result; fn connector_mandate_id(&self) -> Option; @@ -666,7 +676,9 @@ impl PaymentsPreProcessingData for types::PaymentsPreProcessingData { fn is_auto_capture(&self) -> Result { match self.capture_method { - Some(enums::CaptureMethod::Automatic) | None => Ok(true), + Some(enums::CaptureMethod::Automatic) + | None + | Some(enums::CaptureMethod::SequentialAutomatic) => Ok(true), Some(enums::CaptureMethod::Manual) => Ok(false), Some(_) => Err(errors::ConnectorError::CaptureMethodNotSupported.into()), } @@ -681,7 +693,7 @@ impl PaymentsPreProcessingData for types::PaymentsPreProcessingData { .clone() .ok_or_else(missing_field_err("webhook_url")) } - fn get_return_url(&self) -> Result { + fn get_router_return_url(&self) -> Result { self.router_return_url .clone() .ok_or_else(missing_field_err("return_url")) @@ -696,12 +708,23 @@ impl PaymentsPreProcessingData for types::PaymentsPreProcessingData { .clone() .ok_or_else(missing_field_err("complete_authorize_url")) } + fn get_redirect_response_payload(&self) -> Result { + self.redirect_response + .as_ref() + .and_then(|res| res.payload.to_owned()) + .ok_or( + errors::ConnectorError::MissingConnectorRedirectionPayload { + field_name: "request.redirect_response.payload", + } + .into(), + ) + } fn connector_mandate_id(&self) -> Option { self.mandate_id .as_ref() .and_then(|mandate_ids| match &mandate_ids.mandate_reference_id { Some(payments::MandateReferenceId::ConnectorMandateId(connector_mandate_ids)) => { - connector_mandate_ids.connector_mandate_id.clone() + connector_mandate_ids.get_connector_mandate_id() } Some(payments::MandateReferenceId::NetworkMandateId(_)) | None @@ -713,6 +736,7 @@ impl PaymentsPreProcessingData for types::PaymentsPreProcessingData { pub trait PaymentsCaptureRequestData { fn is_multiple_capture(&self) -> bool; fn get_browser_info(&self) -> Result; + fn get_capture_method(&self) -> Option; } impl PaymentsCaptureRequestData for types::PaymentsCaptureData { @@ -724,6 +748,9 @@ impl PaymentsCaptureRequestData for types::PaymentsCaptureData { .clone() .ok_or_else(missing_field_err("browser_info")) } + fn get_capture_method(&self) -> Option { + self.capture_method.to_owned() + } } pub trait RevokeMandateRequestData { @@ -764,10 +791,10 @@ pub trait PaymentsAuthorizeRequestData { fn get_browser_info(&self) -> Result; fn get_order_details(&self) -> Result, Error>; fn get_card(&self) -> Result; - fn get_return_url(&self) -> Result; fn connector_mandate_id(&self) -> Option; fn get_optional_network_transaction_id(&self) -> Option; fn is_mandate_payment(&self) -> bool; + fn is_cit_mandate_payment(&self) -> bool; fn is_customer_initiated_mandate_payment(&self) -> bool; fn get_webhook_url(&self) -> Result; fn get_router_return_url(&self) -> Result; @@ -783,6 +810,7 @@ pub trait PaymentsAuthorizeRequestData { fn get_total_surcharge_amount(&self) -> Option; fn get_metadata_as_object(&self) -> Option; fn get_authentication_data(&self) -> Result; + fn get_connector_mandate_request_reference_id(&self) -> Result; } pub trait PaymentMethodTokenizationRequestData { @@ -800,7 +828,9 @@ impl PaymentMethodTokenizationRequestData for types::PaymentMethodTokenizationDa impl PaymentsAuthorizeRequestData for types::PaymentsAuthorizeData { fn is_auto_capture(&self) -> Result { match self.capture_method { - Some(enums::CaptureMethod::Automatic) | None => Ok(true), + Some(enums::CaptureMethod::Automatic) + | None + | Some(enums::CaptureMethod::SequentialAutomatic) => Ok(true), Some(enums::CaptureMethod::Manual) => Ok(false), Some(_) => Err(errors::ConnectorError::CaptureMethodNotSupported.into()), } @@ -828,11 +858,6 @@ impl PaymentsAuthorizeRequestData for types::PaymentsAuthorizeData { _ => Err(missing_field_err("card")()), } } - fn get_return_url(&self) -> Result { - self.router_return_url - .clone() - .ok_or_else(missing_field_err("return_url")) - } fn get_complete_authorize_url(&self) -> Result { self.complete_authorize_url @@ -845,7 +870,7 @@ impl PaymentsAuthorizeRequestData for types::PaymentsAuthorizeData { .as_ref() .and_then(|mandate_ids| match &mandate_ids.mandate_reference_id { Some(payments::MandateReferenceId::ConnectorMandateId(connector_mandate_ids)) => { - connector_mandate_ids.connector_mandate_id.clone() + connector_mandate_ids.get_connector_mandate_id() } Some(payments::MandateReferenceId::NetworkMandateId(_)) | None @@ -877,6 +902,12 @@ impl PaymentsAuthorizeRequestData for types::PaymentsAuthorizeData { .and_then(|mandate_ids| mandate_ids.mandate_reference_id.as_ref()) .is_some() } + fn is_cit_mandate_payment(&self) -> bool { + (self.customer_acceptance.is_some() || self.setup_mandate_details.is_some()) + && self.setup_future_usage.map_or(false, |setup_future_usage| { + setup_future_usage == storage_enums::FutureUsage::OffSession + }) + } fn get_webhook_url(&self) -> Result { self.webhook_url .clone() @@ -963,6 +994,21 @@ impl PaymentsAuthorizeRequestData for types::PaymentsAuthorizeData { .clone() .ok_or_else(missing_field_err("authentication_data")) } + + /// Attempts to retrieve the connector mandate reference ID as a `Result`. + fn get_connector_mandate_request_reference_id(&self) -> Result { + self.mandate_id + .as_ref() + .and_then(|mandate_ids| match &mandate_ids.mandate_reference_id { + Some(payments::MandateReferenceId::ConnectorMandateId(connector_mandate_ids)) => { + connector_mandate_ids.get_connector_mandate_request_reference_id() + } + Some(payments::MandateReferenceId::NetworkMandateId(_)) + | None + | Some(payments::MandateReferenceId::NetworkTokenWithNTI(_)) => None, + }) + .ok_or_else(missing_field_err("connector_mandate_request_reference_id")) + } } pub trait ConnectorCustomerData { @@ -1042,12 +1088,16 @@ pub trait PaymentsCompleteAuthorizeRequestData { fn get_redirect_response_payload(&self) -> Result; fn get_complete_authorize_url(&self) -> Result; fn is_mandate_payment(&self) -> bool; + fn get_connector_mandate_request_reference_id(&self) -> Result; + fn is_cit_mandate_payment(&self) -> bool; } impl PaymentsCompleteAuthorizeRequestData for types::CompleteAuthorizeData { fn is_auto_capture(&self) -> Result { match self.capture_method { - Some(enums::CaptureMethod::Automatic) | None => Ok(true), + Some(enums::CaptureMethod::Automatic) + | None + | Some(enums::CaptureMethod::SequentialAutomatic) => Ok(true), Some(enums::CaptureMethod::Manual) => Ok(false), Some(_) => Err(errors::ConnectorError::CaptureMethodNotSupported.into()), } @@ -1082,6 +1132,26 @@ impl PaymentsCompleteAuthorizeRequestData for types::CompleteAuthorizeData { .and_then(|mandate_ids| mandate_ids.mandate_reference_id.as_ref()) .is_some() } + fn is_cit_mandate_payment(&self) -> bool { + (self.customer_acceptance.is_some() || self.setup_mandate_details.is_some()) + && self.setup_future_usage.map_or(false, |setup_future_usage| { + setup_future_usage == storage_enums::FutureUsage::OffSession + }) + } + /// Attempts to retrieve the connector mandate reference ID as a `Result`. + fn get_connector_mandate_request_reference_id(&self) -> Result { + self.mandate_id + .as_ref() + .and_then(|mandate_ids| match &mandate_ids.mandate_reference_id { + Some(payments::MandateReferenceId::ConnectorMandateId(connector_mandate_ids)) => { + connector_mandate_ids.get_connector_mandate_request_reference_id() + } + Some(payments::MandateReferenceId::NetworkMandateId(_)) + | None + | Some(payments::MandateReferenceId::NetworkTokenWithNTI(_)) => None, + }) + .ok_or_else(missing_field_err("connector_mandate_request_reference_id")) + } } pub trait PaymentsSyncRequestData { @@ -1092,7 +1162,9 @@ pub trait PaymentsSyncRequestData { impl PaymentsSyncRequestData for types::PaymentsSyncData { fn is_auto_capture(&self) -> Result { match self.capture_method { - Some(enums::CaptureMethod::Automatic) | None => Ok(true), + Some(enums::CaptureMethod::Automatic) + | None + | Some(enums::CaptureMethod::SequentialAutomatic) => Ok(true), Some(enums::CaptureMethod::Manual) => Ok(false), Some(_) => Err(errors::ConnectorError::CaptureMethodNotSupported.into()), } @@ -1109,6 +1181,22 @@ impl PaymentsSyncRequestData for types::PaymentsSyncData { } } +pub trait PaymentsPostSessionTokensRequestData { + fn is_auto_capture(&self) -> Result; +} + +impl PaymentsPostSessionTokensRequestData for types::PaymentsPostSessionTokensData { + fn is_auto_capture(&self) -> Result { + match self.capture_method { + Some(enums::CaptureMethod::Automatic) + | None + | Some(enums::CaptureMethod::SequentialAutomatic) => Ok(true), + Some(enums::CaptureMethod::Manual) => Ok(false), + Some(_) => Err(errors::ConnectorError::CaptureMethodNotSupported.into()), + } + } +} + #[cfg(feature = "payouts")] pub trait CustomerDetails { fn get_customer_id(&self) -> Result; @@ -1427,6 +1515,81 @@ impl CardData for payouts::CardPayout { } } +impl CardData + for hyperswitch_domain_models::payment_method_data::CardDetailsForNetworkTransactionId +{ + fn get_card_expiry_year_2_digit(&self) -> Result, errors::ConnectorError> { + let binding = self.card_exp_year.clone(); + let year = binding.peek(); + Ok(Secret::new( + year.get(year.len() - 2..) + .ok_or(errors::ConnectorError::RequestEncodingFailed)? + .to_string(), + )) + } + fn get_card_issuer(&self) -> Result { + get_card_issuer(self.card_number.peek()) + } + fn get_card_expiry_month_year_2_digit_with_delimiter( + &self, + delimiter: String, + ) -> Result, errors::ConnectorError> { + let year = self.get_card_expiry_year_2_digit()?; + Ok(Secret::new(format!( + "{}{}{}", + self.card_exp_month.peek(), + delimiter, + year.peek() + ))) + } + fn get_expiry_date_as_yyyymm(&self, delimiter: &str) -> Secret { + let year = self.get_expiry_year_4_digit(); + Secret::new(format!( + "{}{}{}", + year.peek(), + delimiter, + self.card_exp_month.peek() + )) + } + fn get_expiry_date_as_mmyyyy(&self, delimiter: &str) -> Secret { + let year = self.get_expiry_year_4_digit(); + Secret::new(format!( + "{}{}{}", + self.card_exp_month.peek(), + delimiter, + year.peek() + )) + } + fn get_expiry_year_4_digit(&self) -> Secret { + let mut year = self.card_exp_year.peek().clone(); + if year.len() == 2 { + year = format!("20{}", year); + } + Secret::new(year) + } + fn get_expiry_date_as_yymm(&self) -> Result, errors::ConnectorError> { + let year = self.get_card_expiry_year_2_digit()?.expose(); + let month = self.card_exp_month.clone().expose(); + Ok(Secret::new(format!("{year}{month}"))) + } + fn get_expiry_month_as_i8(&self) -> Result, Error> { + self.card_exp_month + .peek() + .clone() + .parse::() + .change_context(errors::ConnectorError::ResponseDeserializationFailed) + .map(Secret::new) + } + fn get_expiry_year_as_i32(&self) -> Result, Error> { + self.card_exp_year + .peek() + .clone() + .parse::() + .change_context(errors::ConnectorError::ResponseDeserializationFailed) + .map(Secret::new) + } +} + impl CardData for domain::Card { fn get_card_expiry_year_2_digit(&self) -> Result, errors::ConnectorError> { let binding = self.card_exp_year.clone(); @@ -1626,7 +1789,7 @@ pub trait PhoneDetailsData { fn extract_country_code(&self) -> Result; } -impl PhoneDetailsData for api::PhoneDetails { +impl PhoneDetailsData for hyperswitch_domain_models::address::PhoneDetails { fn get_country_code(&self) -> Result { self.country_code .clone() @@ -1675,7 +1838,7 @@ pub trait AddressDetailsData { fn get_optional_country(&self) -> Option; } -impl AddressDetailsData for api::AddressDetails { +impl AddressDetailsData for hyperswitch_domain_models::address::AddressDetails { fn get_first_name(&self) -> Result<&Secret, Error> { self.first_name .as_ref() @@ -1819,8 +1982,7 @@ pub trait MandateReferenceData { impl MandateReferenceData for payments::ConnectorMandateReferenceId { fn get_connector_mandate_id(&self) -> Result { - self.connector_mandate_id - .clone() + self.get_connector_mandate_id() .ok_or_else(missing_field_err("mandate_id")) } } @@ -2330,31 +2492,13 @@ impl FraudCheckRecordReturnRequest for fraud_check::FraudCheckRecordReturnData { } } -pub trait AccessPaymentAttemptInfo { - fn get_browser_info( - &self, - ) -> Result, error_stack::Report>; -} - -impl AccessPaymentAttemptInfo for PaymentAttempt { - fn get_browser_info( - &self, - ) -> Result, error_stack::Report> { - self.browser_info - .clone() - .map(|b| b.parse_value("BrowserInformation")) - .transpose() - .change_context(ApiErrorResponse::InvalidDataValue { - field_name: "browser_info", - }) - } -} - +#[cfg(feature = "v1")] pub trait PaymentsAttemptData { fn get_browser_info(&self) -> Result>; } +#[cfg(feature = "v1")] impl PaymentsAttemptData for PaymentAttempt { fn get_browser_info( &self, @@ -2626,6 +2770,7 @@ pub enum PaymentMethodDataType { MobilePayRedirect, PaypalRedirect, PaypalSdk, + Paze, SamsungPay, TwintRedirect, VippsRedirect, @@ -2636,6 +2781,7 @@ pub enum PaymentMethodDataType { SwishQr, KlarnaRedirect, KlarnaSdk, + KlarnaCheckout, AffirmRedirect, AfterpayClearpayRedirect, PayBrightRedirect, @@ -2705,6 +2851,7 @@ pub enum PaymentMethodDataType { VietQr, OpenBanking, NetworkToken, + DirectCarrierBilling, } impl From for PaymentMethodDataType { @@ -2712,6 +2859,7 @@ impl From for PaymentMethodDataType { match pm_data { domain::payments::PaymentMethodData::Card(_) => Self::Card, domain::payments::PaymentMethodData::NetworkToken(_) => Self::NetworkToken, + domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => Self::Card, domain::payments::PaymentMethodData::CardRedirect(card_redirect_data) => { match card_redirect_data { domain::CardRedirectData::Knet {} => Self::Knet, @@ -2743,6 +2891,7 @@ impl From for PaymentMethodDataType { domain::payments::WalletData::MobilePayRedirect(_) => Self::MobilePayRedirect, domain::payments::WalletData::PaypalRedirect(_) => Self::PaypalRedirect, domain::payments::WalletData::PaypalSdk(_) => Self::PaypalSdk, + domain::payments::WalletData::Paze(_) => Self::Paze, domain::payments::WalletData::SamsungPay(_) => Self::SamsungPay, domain::payments::WalletData::TwintRedirect {} => Self::TwintRedirect, domain::payments::WalletData::VippsRedirect {} => Self::VippsRedirect, @@ -2756,6 +2905,7 @@ impl From for PaymentMethodDataType { 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::KlarnaCheckout {} => Self::KlarnaCheckout, domain::payments::PayLaterData::AffirmRedirect {} => Self::AffirmRedirect, domain::payments::PayLaterData::AfterpayClearpayRedirect { .. } => { Self::AfterpayClearpayRedirect @@ -2889,6 +3039,9 @@ impl From for PaymentMethodDataType { domain::payments::PaymentMethodData::OpenBanking(data) => match data { hyperswitch_domain_models::payment_method_data::OpenBankingData::OpenBankingPIS { } => Self::OpenBanking }, + domain::payments::PaymentMethodData::MobilePayment(mobile_payment_data) => match mobile_payment_data { + hyperswitch_domain_models::payment_method_data::MobilePaymentData::DirectCarrierBilling { .. } => Self::DirectCarrierBilling, + }, } } } @@ -3019,3 +3172,14 @@ impl NetworkTokenData for domain::NetworkTokenData { Secret::new(year) } } + +pub fn convert_uppercase<'de, D, T>(v: D) -> Result +where + D: serde::Deserializer<'de>, + T: FromStr, + ::Err: std::fmt::Debug + std::fmt::Display + std::error::Error, +{ + use serde::de::Error; + let output = <&str>::deserialize(v)?; + output.to_uppercase().parse::().map_err(D::Error::custom) +} diff --git a/crates/router/src/connector/wellsfargo.rs b/crates/router/src/connector/wellsfargo.rs index f5cdb6431b35..b47b3662253e 100644 --- a/crates/router/src/connector/wellsfargo.rs +++ b/crates/router/src/connector/wellsfargo.rs @@ -1,9 +1,10 @@ pub mod transformers; -use std::fmt::Debug; - use base64::Engine; -use common_utils::request::RequestContent; +use common_utils::{ + request::RequestContent, + types::{AmountConvertor, MinorUnit, StringMajorUnit, StringMajorUnitForConnector}, +}; use diesel_models::enums; use error_stack::{report, Report, ResultExt}; use masking::{ExposeInterface, PeekInterface}; @@ -12,6 +13,7 @@ use time::OffsetDateTime; use transformers as wellsfargo; use url::Url; +use super::utils::convert_amount; use crate::{ configs::settings, connector::{ @@ -25,7 +27,7 @@ use crate::{ services::{ self, request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, + ConnectorIntegration, ConnectorSpecifications, ConnectorValidation, }, types::{ self, @@ -35,10 +37,18 @@ use crate::{ utils::BytesExt, }; -#[derive(Debug, Clone)] -pub struct Wellsfargo; +#[derive(Clone)] +pub struct Wellsfargo { + amount_converter: &'static (dyn AmountConvertor + Sync), +} impl Wellsfargo { + pub fn new() -> &'static Self { + &Self { + amount_converter: &StringMajorUnitForConnector, + } + } + pub fn generate_digest(&self, payload: &[u8]) -> String { let payload_digest = digest::digest(&digest::SHA256, payload); consts::BASE64_ENGINE.encode(payload_digest) @@ -235,14 +245,17 @@ impl ConnectorCommon for Wellsfargo { } impl ConnectorValidation for Wellsfargo { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + _payment_method: enums::PaymentMethod, _pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { let capture_method = capture_method.unwrap_or_default(); match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::Automatic + | enums::CaptureMethod::Manual + | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( connector_utils::construct_not_implemented_error_report(capture_method, self.id()), ), @@ -600,12 +613,14 @@ impl ConnectorIntegration CustomResult { - let connector_router_data = wellsfargo::WellsfargoRouterData::try_from(( - &self.get_currency_unit(), + let amount = convert_amount( + self.amount_converter, + MinorUnit::new(req.request.amount_to_capture), req.request.currency, - req.request.amount_to_capture, - req, - ))?; + )?; + + let connector_router_data = wellsfargo::WellsfargoRouterData::from((amount, req)); + let connector_req = wellsfargo::WellsfargoPaymentsCaptureRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -792,12 +807,13 @@ impl ConnectorIntegration CustomResult { - let connector_router_data = wellsfargo::WellsfargoRouterData::try_from(( - &self.get_currency_unit(), + let amount = convert_amount( + self.amount_converter, + MinorUnit::new(req.request.amount), req.request.currency, - req.request.amount, - req, - ))?; + )?; + + let connector_router_data = wellsfargo::WellsfargoRouterData::from((amount, req)); let connector_req = wellsfargo::WellsfargoPaymentsRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -915,20 +931,21 @@ impl ConnectorIntegration CustomResult { - let connector_router_data = wellsfargo::WellsfargoRouterData::try_from(( - &self.get_currency_unit(), + let amount = convert_amount( + self.amount_converter, + MinorUnit::new(req.request.amount.ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "Amount", + }, + )?), req.request .currency .ok_or(errors::ConnectorError::MissingRequiredField { field_name: "Currency", })?, - req.request - .amount - .ok_or(errors::ConnectorError::MissingRequiredField { - field_name: "Amount", - })?, - req, - ))?; + )?; + + let connector_router_data = wellsfargo::WellsfargoRouterData::from((amount, req)); let connector_req = wellsfargo::WellsfargoVoidRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -1040,12 +1057,13 @@ impl ConnectorIntegration CustomResult { - let connector_router_data = wellsfargo::WellsfargoRouterData::try_from(( - &self.get_currency_unit(), + let amount = convert_amount( + self.amount_converter, + MinorUnit::new(req.request.refund_amount), req.request.currency, - req.request.refund_amount, - req, - ))?; + )?; + + let connector_router_data = wellsfargo::WellsfargoRouterData::from((amount, req)); let connector_req = wellsfargo::WellsfargoRefundRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -1204,12 +1222,13 @@ impl req: &types::PaymentsIncrementalAuthorizationRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_router_data = wellsfargo::WellsfargoRouterData::try_from(( - &self.get_currency_unit(), + let amount = convert_amount( + self.amount_converter, + MinorUnit::new(req.request.additional_amount), req.request.currency, - req.request.additional_amount, - req, - ))?; + )?; + + let connector_router_data = wellsfargo::WellsfargoRouterData::from((amount, req)); let connector_request = wellsfargo::WellsfargoPaymentsIncrementalAuthorizationRequest::try_from( &connector_router_data, @@ -1298,3 +1317,5 @@ impl api::IncomingWebhook for Wellsfargo { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for Wellsfargo {} diff --git a/crates/router/src/connector/wellsfargo/transformers.rs b/crates/router/src/connector/wellsfargo/transformers.rs index be3cf4a1cef9..937af33bd798 100644 --- a/crates/router/src/connector/wellsfargo/transformers.rs +++ b/crates/router/src/connector/wellsfargo/transformers.rs @@ -1,7 +1,10 @@ use api_models::payments; use base64::Engine; use common_enums::FutureUsage; -use common_utils::{pii, types::SemanticVersion}; +use common_utils::{ + pii, + types::{SemanticVersion, StringMajorUnit}, +}; use masking::{ExposeInterface, PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -26,21 +29,16 @@ use crate::{ #[derive(Debug, Serialize)] pub struct WellsfargoRouterData { - pub amount: String, + pub amount: StringMajorUnit, pub router_data: T, } -impl TryFrom<(&api::CurrencyUnit, enums::Currency, i64, T)> for WellsfargoRouterData { - type Error = error_stack::Report; - fn try_from( - (currency_unit, currency, amount, item): (&api::CurrencyUnit, enums::Currency, i64, T), - ) -> Result { - // This conversion function is used at different places in the file, if updating this, keep a check for those - let amount = utils::get_amount_as_string(currency_unit, amount, currency)?; - Ok(Self { +impl From<(StringMajorUnit, T)> for WellsfargoRouterData { + fn from((amount, router_data): (StringMajorUnit, T)) -> Self { + Self { amount, - router_data: item, - }) + router_data, + } } } @@ -61,7 +59,7 @@ impl TryFrom<&types::SetupMandateRouterData> for WellsfargoZeroMandateRequest { let order_information = OrderInformationWithBill { amount_details: Amount { - total_amount: "0".to_string(), + total_amount: StringMajorUnit::zero(), currency: item.request.currency, }, bill_to: Some(bill_to), @@ -135,6 +133,9 @@ impl TryFrom<&types::SetupMandateRouterData> for WellsfargoZeroMandateRequest { types::PaymentMethodToken::Token(_) => Err( unimplemented_payment_method!("Apple Pay", "Manual", "Wellsfargo"), )?, + types::PaymentMethodToken::PazeDecrypt(_) => { + Err(unimplemented_payment_method!("Paze", "Wellsfargo"))? + } }, None => ( PaymentInformation::ApplePayToken(Box::new( @@ -180,6 +181,7 @@ impl TryFrom<&types::SetupMandateRouterData> for WellsfargoZeroMandateRequest { | domain::WalletData::MobilePayRedirect(_) | domain::WalletData::PaypalRedirect(_) | domain::WalletData::PaypalSdk(_) + | domain::WalletData::Paze(_) | domain::WalletData::SamsungPay(_) | domain::WalletData::TwintRedirect {} | domain::WalletData::VippsRedirect {} @@ -201,12 +203,14 @@ impl TryFrom<&types::SetupMandateRouterData> for WellsfargoZeroMandateRequest { | domain::PaymentMethodData::MandatePayment | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Wellsfargo"), ))? @@ -467,14 +471,14 @@ pub struct OrderInformation { #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct Amount { - total_amount: String, + total_amount: StringMajorUnit, currency: api_models::enums::Currency, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct AdditionalAmount { - additional_amount: String, + additional_amount: StringMajorUnit, currency: String, } @@ -713,7 +717,9 @@ impl Ok(Self { capture: Some(matches!( item.router_data.request.capture_method, - Some(enums::CaptureMethod::Automatic) | None + Some(enums::CaptureMethod::Automatic) + | Some(enums::CaptureMethod::SequentialAutomatic) + | None )), payment_solution: solution.map(String::from), action_list, @@ -798,7 +804,9 @@ impl } } -fn get_phone_number(item: Option<&payments::Address>) -> Option> { +fn get_phone_number( + item: Option<&hyperswitch_domain_models::address::Address>, +) -> Option> { item.as_ref() .and_then(|billing| billing.phone.as_ref()) .and_then(|phone| { @@ -812,7 +820,7 @@ fn get_phone_number(item: Option<&payments::Address>) -> Option> } fn build_bill_to( - address_details: Option<&payments::Address>, + address_details: Option<&hyperswitch_domain_models::address::Address>, email: pii::Email, ) -> Result> { let phone_number = get_phone_number(address_details); @@ -1158,6 +1166,9 @@ impl TryFrom<&WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>> "Wellsfargo" ))? } + types::PaymentMethodToken::PazeDecrypt(_) => { + Err(unimplemented_payment_method!("Paze", "Wellsfargo"))? + } }, None => { let email = item.router_data.request.get_email()?; @@ -1242,6 +1253,7 @@ impl TryFrom<&WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>> | domain::WalletData::MobilePayRedirect(_) | domain::WalletData::PaypalRedirect(_) | domain::WalletData::PaypalSdk(_) + | domain::WalletData::Paze(_) | domain::WalletData::SamsungPay(_) | domain::WalletData::TwintRedirect {} | domain::WalletData::VippsRedirect {} @@ -1278,12 +1290,14 @@ impl TryFrom<&WellsfargoRouterData<&types::PaymentsAuthorizeRouterData>> | domain::PaymentMethodData::Crypto(_) | domain::PaymentMethodData::Reward | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::MobilePayment(_) | domain::PaymentMethodData::Upi(_) | domain::PaymentMethodData::Voucher(_) | domain::PaymentMethodData::GiftCard(_) | domain::PaymentMethodData::OpenBanking(_) | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { + | domain::PaymentMethodData::NetworkToken(_) + | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Wellsfargo"), ) @@ -1779,12 +1793,13 @@ fn get_payment_response( .map(|payment_instrument| payment_instrument.id.expose()), payment_method_id: None, mandate_metadata: None, + connector_mandate_request_reference_id: None, }); Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(info_response.id.clone()), - redirection_data: None, - mandate_reference, + redirection_data: Box::new(None), + mandate_reference: Box::new(mandate_reference), connector_metadata: None, network_txn_id: info_response.processor_information.as_ref().and_then( |processor_information| processor_information.network_transaction_id.clone(), @@ -1965,6 +1980,7 @@ impl .map(|payment_instrument| payment_instrument.id.expose()), payment_method_id: None, mandate_metadata: None, + connector_mandate_request_reference_id: None, }); let mut mandate_status = enums::AttemptStatus::foreign_from(( item.response @@ -1995,8 +2011,8 @@ impl resource_id: types::ResponseId::ConnectorTransactionId( item.response.id.clone(), ), - redirection_data: None, - mandate_reference, + redirection_data: Box::new(None), + mandate_reference: Box::new(mandate_reference), connector_metadata: None, network_txn_id: item.response.processor_information.as_ref().and_then( |processor_information| { @@ -2133,8 +2149,8 @@ impl resource_id: types::ResponseId::ConnectorTransactionId( item.response.id.clone(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: item @@ -2155,8 +2171,8 @@ impl resource_id: types::ResponseId::ConnectorTransactionId( item.response.id.clone(), ), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: Some(item.response.id), diff --git a/crates/router/src/connector/wellsfargopayout.rs b/crates/router/src/connector/wellsfargopayout.rs index 51b303494bbb..33111ee7be05 100644 --- a/crates/router/src/connector/wellsfargopayout.rs +++ b/crates/router/src/connector/wellsfargopayout.rs @@ -3,9 +3,9 @@ pub mod transformers; use common_utils::types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}; use error_stack::{report, ResultExt}; use masking::ExposeInterface; -use transformers as wellsfargopayout; -use super::utils::{self as connector_utils}; +use self::transformers as wellsfargopayout; +use super::utils as connector_utils; use crate::{ configs::settings, core::errors::{self, CustomResult}, @@ -14,7 +14,7 @@ use crate::{ services::{ self, request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, + ConnectorIntegration, ConnectorSpecifications, ConnectorValidation, }, types::{ self, @@ -581,3 +581,5 @@ impl api::IncomingWebhook for Wellsfargopayout { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for Wellsfargopayout {} diff --git a/crates/router/src/connector/wellsfargopayout/transformers.rs b/crates/router/src/connector/wellsfargopayout/transformers.rs index e335c3cf59ae..135cb5f53cbf 100644 --- a/crates/router/src/connector/wellsfargopayout/transformers.rs +++ b/crates/router/src/connector/wellsfargopayout/transformers.rs @@ -134,8 +134,8 @@ impl status: enums::AttemptStatus::from(item.response.status), response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(item.response.id), - redirection_data: None, - mandate_reference: None, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, diff --git a/crates/router/src/connector/wise.rs b/crates/router/src/connector/wise.rs index 8123ae7ec796..e4493f5016f8 100644 --- a/crates/router/src/connector/wise.rs +++ b/crates/router/src/connector/wise.rs @@ -1,8 +1,8 @@ pub mod transformers; -use std::fmt::Debug; #[cfg(feature = "payouts")] use common_utils::request::RequestContent; +use common_utils::types::{AmountConvertor, MinorUnit, MinorUnitForConnector}; use error_stack::{report, ResultExt}; #[cfg(feature = "payouts")] use masking::PeekInterface; @@ -10,6 +10,7 @@ use masking::PeekInterface; use router_env::{instrument, tracing}; use self::transformers as wise; +use super::utils::convert_amount; use crate::{ configs::settings, core::errors::{self, CustomResult}, @@ -18,7 +19,7 @@ use crate::{ services::{ self, request::{self, Mask}, - ConnectorValidation, + ConnectorSpecifications, ConnectorValidation, }, types::{ self, @@ -27,8 +28,18 @@ use crate::{ utils::BytesExt, }; -#[derive(Debug, Clone)] -pub struct Wise; +#[derive(Clone)] +pub struct Wise { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl Wise { + pub fn new() -> &'static Self { + &Self { + amount_converter: &MinorUnitForConnector, + } + } +} impl ConnectorCommonExt for Wise where @@ -362,7 +373,13 @@ impl services::ConnectorIntegration, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_req = wise::WisePayoutQuoteRequest::try_from(req)?; + let amount = convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.source_currency, + )?; + let connector_router_data = wise::WiseRouterData::from((amount, req)); + let connector_req = wise::WisePayoutQuoteRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -441,7 +458,13 @@ impl req: &types::PayoutsRouterData, _connectors: &settings::Connectors, ) -> CustomResult { - let connector_req = wise::WiseRecipientCreateRequest::try_from(req)?; + let amount = convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.source_currency, + )?; + let connector_router_data = wise::WiseRouterData::from((amount, req)); + let connector_req = wise::WiseRecipientCreateRequest::try_from(&connector_router_data)?; Ok(RequestContent::Json(Box::new(connector_req))) } @@ -725,3 +748,5 @@ impl api::IncomingWebhook for Wise { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } } + +impl ConnectorSpecifications for Wise {} diff --git a/crates/router/src/connector/wise/transformers.rs b/crates/router/src/connector/wise/transformers.rs index fe82ffa59547..513a762bfd3c 100644 --- a/crates/router/src/connector/wise/transformers.rs +++ b/crates/router/src/connector/wise/transformers.rs @@ -2,6 +2,7 @@ use api_models::payouts::PayoutMethodData; #[cfg(feature = "payouts")] use common_utils::pii::Email; +use common_utils::types::MinorUnit; use masking::Secret; use serde::{Deserialize, Serialize}; @@ -16,6 +17,20 @@ use crate::{ }, }; use crate::{core::errors, types}; +#[derive(Debug, Serialize)] +pub struct WiseRouterData { + pub amount: MinorUnit, + pub router_data: T, +} + +impl From<(MinorUnit, T)> for WiseRouterData { + fn from((amount, router_data): (MinorUnit, T)) -> Self { + Self { + amount, + router_data, + } + } +} pub struct WiseAuthType { pub(super) api_key: Secret, @@ -156,8 +171,8 @@ pub struct WiseRecipientCreateResponse { pub struct WisePayoutQuoteRequest { source_currency: String, target_currency: String, - source_amount: Option, - target_amount: Option, + source_amount: Option, + target_amount: Option, pay_out: WisePayOutOption, } @@ -291,7 +306,7 @@ pub enum WiseStatus { #[cfg(feature = "payouts")] fn get_payout_address_details( - address: Option<&api_models::payments::Address>, + address: Option<&hyperswitch_domain_models::address::Address>, ) -> Option { address.and_then(|add| { add.address.as_ref().map(|a| WiseAddressDetails { @@ -308,7 +323,7 @@ fn get_payout_address_details( #[cfg(feature = "payouts")] fn get_payout_bank_details( payout_method_data: PayoutMethodData, - address: Option<&api_models::payments::Address>, + address: Option<&hyperswitch_domain_models::address::Address>, entity_type: PayoutEntityType, ) -> Result { let wise_address_details = match get_payout_address_details(address) { @@ -348,9 +363,12 @@ fn get_payout_bank_details( // Payouts recipient create request transform #[cfg(feature = "payouts")] -impl TryFrom<&types::PayoutsRouterData> for WiseRecipientCreateRequest { +impl TryFrom<&WiseRouterData<&types::PayoutsRouterData>> for WiseRecipientCreateRequest { type Error = Error; - fn try_from(item: &types::PayoutsRouterData) -> Result { + fn try_from( + item_data: &WiseRouterData<&types::PayoutsRouterData>, + ) -> Result { + let item = item_data.router_data; let request = item.request.to_owned(); let customer_details = request.customer_details.to_owned(); let payout_method_data = item.get_payout_method_data()?; @@ -420,14 +438,17 @@ impl TryFrom // Payouts quote request transform #[cfg(feature = "payouts")] -impl TryFrom<&types::PayoutsRouterData> for WisePayoutQuoteRequest { +impl TryFrom<&WiseRouterData<&types::PayoutsRouterData>> for WisePayoutQuoteRequest { type Error = Error; - fn try_from(item: &types::PayoutsRouterData) -> Result { + fn try_from( + item_data: &WiseRouterData<&types::PayoutsRouterData>, + ) -> Result { + let item = item_data.router_data; let request = item.request.to_owned(); let payout_type = request.get_payout_type()?; match payout_type { storage_enums::PayoutType::Bank => Ok(Self { - source_amount: Some(request.amount), + source_amount: Some(item_data.amount), source_currency: request.source_currency.to_string(), target_amount: None, target_currency: request.destination_currency.to_string(), diff --git a/crates/router/src/connector/worldpay.rs b/crates/router/src/connector/worldpay.rs deleted file mode 100644 index 33c9393cede3..000000000000 --- a/crates/router/src/connector/worldpay.rs +++ /dev/null @@ -1,797 +0,0 @@ -mod requests; -mod response; -pub mod transformers; - -use std::fmt::Debug; - -use common_utils::{crypto, ext_traits::ByteSliceExt, request::RequestContent}; -use diesel_models::enums; -use error_stack::ResultExt; -use transformers as worldpay; - -use self::{requests::*, response::*}; -use super::utils::{self as connector_utils, RefundsRequestData}; -use crate::{ - configs::settings, - core::errors::{self, CustomResult}, - events::connector_api_logs::ConnectorEvent, - headers, - services::{ - self, - request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, - }, - types::{ - self, - api::{self, ConnectorCommon, ConnectorCommonExt}, - transformers::ForeignTryFrom, - ErrorResponse, Response, - }, - utils::BytesExt, -}; - -#[derive(Debug, Clone)] -pub struct Worldpay; - -impl ConnectorCommonExt for Worldpay -where - Self: ConnectorIntegration, -{ - fn build_headers( - &self, - req: &types::RouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - let mut headers = vec![( - headers::CONTENT_TYPE.to_string(), - self.get_content_type().to_string().into(), - )]; - let mut api_key = self.get_auth_header(&req.connector_auth_type)?; - headers.append(&mut api_key); - Ok(headers) - } -} - -impl ConnectorCommon for Worldpay { - fn id(&self) -> &'static str { - "worldpay" - } - - fn get_currency_unit(&self) -> api::CurrencyUnit { - api::CurrencyUnit::Minor - } - - fn common_get_content_type(&self) -> &'static str { - "application/vnd.worldpay.payments-v6+json" - } - - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { - connectors.worldpay.base_url.as_ref() - } - - fn get_auth_header( - &self, - auth_type: &types::ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { - let auth = worldpay::WorldpayAuthType::try_from(auth_type) - .change_context(errors::ConnectorError::FailedToObtainAuthType)?; - Ok(vec![( - headers::AUTHORIZATION.to_string(), - auth.api_key.into_masked(), - )]) - } - - fn build_error_response( - &self, - res: Response, - event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { - let response: WorldpayErrorResponse = res - .response - .parse_struct("WorldpayErrorResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - - event_builder.map(|i| i.set_error_response_body(&response)); - router_env::logger::info!(connector_response=?response); - - Ok(ErrorResponse { - status_code: res.status_code, - code: response.error_name, - message: response.message, - reason: response.validation_errors.map(|e| e.to_string()), - attempt_status: None, - connector_transaction_id: None, - }) - } -} - -impl ConnectorValidation for Worldpay { - fn validate_capture_method( - &self, - capture_method: Option, - _pmt: Option, - ) -> CustomResult<(), errors::ConnectorError> { - let capture_method = capture_method.unwrap_or_default(); - match capture_method { - enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), - enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( - connector_utils::construct_not_implemented_error_report(capture_method, self.id()), - ), - } - } -} - -impl api::Payment for Worldpay {} - -impl api::MandateSetup for Worldpay {} -impl - ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Worldpay -{ - fn build_request( - &self, - _req: &types::RouterData< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - >, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - Err( - errors::ConnectorError::NotImplemented("Setup Mandate flow for Worldpay".to_string()) - .into(), - ) - } -} - -impl api::PaymentToken for Worldpay {} - -impl - ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Worldpay -{ - // Not Implemented (R) -} - -impl api::PaymentVoid for Worldpay {} - -impl ConnectorIntegration - for Worldpay -{ - fn get_headers( - &self, - req: &types::PaymentsCancelRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) - } - - fn get_content_type(&self) -> &'static str { - self.common_get_content_type() - } - - fn get_url( - &self, - req: &types::PaymentsCancelRouterData, - connectors: &settings::Connectors, - ) -> CustomResult { - let connector_payment_id = req.request.connector_transaction_id.clone(); - Ok(format!( - "{}payments/settlements/{}", - self.base_url(connectors), - connector_payment_id - )) - } - - fn build_request( - &self, - req: &types::PaymentsCancelRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) - .build(), - )) - } - - fn handle_response( - &self, - data: &types::PaymentsCancelRouterData, - event_builder: Option<&mut ConnectorEvent>, - res: Response, - ) -> CustomResult - where - api::Void: Clone, - types::PaymentsCancelData: Clone, - types::PaymentsResponseData: Clone, - { - match res.status_code { - 202 => { - let response: WorldpayPaymentsResponse = res - .response - .parse_struct("Worldpay PaymentsResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - event_builder.map(|i| i.set_response_body(&response)); - router_env::logger::info!(connector_response=?response); - Ok(types::PaymentsCancelRouterData { - status: enums::AttemptStatus::Voided, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::foreign_try_from(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, - }), - ..data.clone() - }) - } - _ => Err(errors::ConnectorError::ResponseHandlingFailed)?, - } - } - - fn get_error_response( - &self, - res: Response, - event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { - self.build_error_response(res, event_builder) - } -} - -impl api::ConnectorAccessToken for Worldpay {} - -impl ConnectorIntegration - for Worldpay -{ -} - -impl api::PaymentSync for Worldpay {} -impl ConnectorIntegration - for Worldpay -{ - fn get_headers( - &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) - } - - fn get_content_type(&self) -> &'static str { - self.common_get_content_type() - } - - fn get_url( - &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult { - let connector_payment_id = req - .request - .connector_transaction_id - .get_connector_transaction_id() - .change_context(errors::ConnectorError::MissingConnectorTransactionID)?; - Ok(format!( - "{}payments/events/{}", - self.base_url(connectors), - connector_payment_id - )) - } - - fn build_request( - &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) - .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) - .build(), - )) - } - - fn get_error_response( - &self, - res: Response, - event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { - self.build_error_response(res, event_builder) - } - - fn handle_response( - &self, - data: &types::PaymentsSyncRouterData, - event_builder: Option<&mut ConnectorEvent>, - res: Response, - ) -> CustomResult { - let response: WorldpayEventResponse = - res.response - .parse_struct("Worldpay EventResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - - event_builder.map(|i| i.set_response_body(&response)); - router_env::logger::info!(connector_response=?response); - - Ok(types::PaymentsSyncRouterData { - status: enums::AttemptStatus::from(response.last_event), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: data.request.connector_transaction_id.clone(), - redirection_data: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: None, - incremental_authorization_allowed: None, - charge_id: None, - }), - ..data.clone() - }) - } -} - -impl api::PaymentCapture for Worldpay {} -impl ConnectorIntegration - for Worldpay -{ - fn get_headers( - &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) - } - - fn get_content_type(&self) -> &'static str { - self.common_get_content_type() - } - - fn build_request( - &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::PaymentsCaptureType::get_headers( - self, req, connectors, - )?) - .build(), - )) - } - - fn handle_response( - &self, - data: &types::PaymentsCaptureRouterData, - event_builder: Option<&mut ConnectorEvent>, - res: Response, - ) -> CustomResult { - match res.status_code { - 202 => { - let response: WorldpayPaymentsResponse = res - .response - .parse_struct("Worldpay PaymentsResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - event_builder.map(|i| i.set_response_body(&response)); - router_env::logger::info!(connector_response=?response); - Ok(types::PaymentsCaptureRouterData { - status: enums::AttemptStatus::Charged, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::foreign_try_from(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, - }), - ..data.clone() - }) - } - _ => Err(errors::ConnectorError::ResponseHandlingFailed)?, - } - } - - fn get_url( - &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult { - let connector_payment_id = req.request.connector_transaction_id.clone(); - Ok(format!( - "{}payments/settlements/{}", - self.base_url(connectors), - connector_payment_id - )) - } - - fn get_error_response( - &self, - res: Response, - event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { - self.build_error_response(res, event_builder) - } -} - -impl api::PaymentSession for Worldpay {} - -impl ConnectorIntegration - for Worldpay -{ -} - -impl api::PaymentAuthorize for Worldpay {} - -impl ConnectorIntegration - for Worldpay -{ - fn get_headers( - &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) - } - - fn get_content_type(&self) -> &'static str { - self.common_get_content_type() - } - - fn get_url( - &self, - _req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult { - Ok(format!( - "{}payments/authorizations", - self.base_url(connectors) - )) - } - - fn get_request_body( - &self, - req: &types::PaymentsAuthorizeRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult { - let connector_router_data = worldpay::WorldpayRouterData::try_from(( - &self.get_currency_unit(), - req.request.currency, - req.request.amount, - req, - ))?; - let connector_req = WorldpayPaymentsRequest::try_from(&connector_router_data)?; - Ok(RequestContent::Json(Box::new(connector_req))) - } - - fn build_request( - &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsAuthorizeType::get_url( - self, req, connectors, - )?) - .attach_default_headers() - .headers(types::PaymentsAuthorizeType::get_headers( - self, req, connectors, - )?) - .set_body(types::PaymentsAuthorizeType::get_request_body( - self, req, connectors, - )?) - .build(), - )) - } - - fn handle_response( - &self, - data: &types::PaymentsAuthorizeRouterData, - event_builder: Option<&mut ConnectorEvent>, - res: Response, - ) -> CustomResult { - let response: WorldpayPaymentsResponse = res - .response - .parse_struct("Worldpay PaymentsResponse") - .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, - }) - .change_context(errors::ConnectorError::ResponseHandlingFailed) - } - - fn get_error_response( - &self, - res: Response, - event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { - self.build_error_response(res, event_builder) - } -} - -impl api::Refund for Worldpay {} -impl api::RefundExecute for Worldpay {} -impl api::RefundSync for Worldpay {} - -impl ConnectorIntegration - for Worldpay -{ - fn get_headers( - &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) - } - - fn get_content_type(&self) -> &'static str { - self.common_get_content_type() - } - - fn get_request_body( - &self, - req: &types::RefundExecuteRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult { - let connector_req = WorldpayRefundRequest::try_from(req)?; - Ok(RequestContent::Json(Box::new(connector_req))) - } - - fn get_url( - &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult { - let connector_payment_id = req.request.connector_transaction_id.clone(); - Ok(format!( - "{}payments/settlements/refunds/partials/{}", - self.base_url(connectors), - connector_payment_id - )) - } - - fn build_request( - &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::RefundExecuteType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::RefundExecuteType::get_headers( - self, req, connectors, - )?) - .set_body(types::RefundExecuteType::get_request_body( - self, req, connectors, - )?) - .build(); - Ok(Some(request)) - } - - fn handle_response( - &self, - data: &types::RefundsRouterData, - event_builder: Option<&mut ConnectorEvent>, - res: Response, - ) -> CustomResult, errors::ConnectorError> { - match res.status_code { - 202 => { - let response: WorldpayPaymentsResponse = res - .response - .parse_struct("Worldpay PaymentsResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - event_builder.map(|i| i.set_response_body(&response)); - router_env::logger::info!(connector_response=?response); - Ok(types::RefundExecuteRouterData { - response: Ok(types::RefundsResponseData { - connector_refund_id: ResponseIdStr::try_from(response.links)?.id, - refund_status: enums::RefundStatus::Success, - }), - ..data.clone() - }) - } - _ => Err(errors::ConnectorError::ResponseHandlingFailed)?, - } - } - - fn get_error_response( - &self, - res: Response, - event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { - self.build_error_response(res, event_builder) - } -} - -impl ConnectorIntegration for Worldpay { - fn get_headers( - &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) - } - - fn get_content_type(&self) -> &'static str { - self.common_get_content_type() - } - - fn get_url( - &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult { - Ok(format!( - "{}payments/events/{}", - self.base_url(connectors), - req.request.get_connector_refund_id()? - )) - } - - fn build_request( - &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) - .url(&types::RefundSyncType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::RefundSyncType::get_headers(self, req, connectors)?) - .build(), - )) - } - - fn handle_response( - &self, - data: &types::RefundSyncRouterData, - event_builder: Option<&mut ConnectorEvent>, - res: Response, - ) -> CustomResult { - let response: WorldpayEventResponse = - res.response - .parse_struct("Worldpay EventResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - event_builder.map(|i| i.set_response_body(&response)); - router_env::logger::info!(connector_response=?response); - Ok(types::RefundSyncRouterData { - response: Ok(types::RefundsResponseData { - connector_refund_id: data.request.refund_id.clone(), - refund_status: enums::RefundStatus::from(response.last_event), - }), - ..data.clone() - }) - } - - fn get_error_response( - &self, - res: Response, - event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { - self.build_error_response(res, event_builder) - } -} - -#[async_trait::async_trait] -impl api::IncomingWebhook for Worldpay { - fn get_webhook_source_verification_algorithm( - &self, - _request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult, errors::ConnectorError> { - Ok(Box::new(crypto::Sha256)) - } - - fn get_webhook_source_verification_signature( - &self, - request: &api::IncomingWebhookRequestDetails<'_>, - _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, - ) -> CustomResult, errors::ConnectorError> { - let event_signature = - connector_utils::get_header_key_value("Event-Signature", request.headers)?.split(','); - let sign_header = event_signature - .last() - .ok_or(errors::ConnectorError::WebhookSignatureNotFound)?; - let signature = sign_header - .split('/') - .last() - .ok_or(errors::ConnectorError::WebhookSignatureNotFound)?; - hex::decode(signature).change_context(errors::ConnectorError::WebhookResponseEncodingFailed) - } - - fn get_webhook_source_verification_message( - &self, - request: &api::IncomingWebhookRequestDetails<'_>, - _merchant_id: &common_utils::id_type::MerchantId, - connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, - ) -> CustomResult, errors::ConnectorError> { - let secret_str = std::str::from_utf8(&connector_webhook_secrets.secret) - .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; - let to_sign = format!( - "{}{}", - secret_str, - std::str::from_utf8(request.body) - .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)? - ); - Ok(to_sign.into_bytes()) - } - - fn get_webhook_object_reference_id( - &self, - request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { - let body: WorldpayWebhookTransactionId = request - .body - .parse_struct("WorldpayWebhookTransactionId") - .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; - Ok(api_models::webhooks::ObjectReferenceId::PaymentId( - api::PaymentIdType::ConnectorTransactionId(body.event_details.transaction_reference), - )) - } - - fn get_webhook_event_type( - &self, - request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { - let body: WorldpayWebhookEventType = request - .body - .parse_struct("WorldpayWebhookEventType") - .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; - match body.event_details.event_type { - EventType::SentForSettlement | EventType::Charged => { - Ok(api::IncomingWebhookEvent::PaymentIntentSuccess) - } - EventType::Error | EventType::Expired => { - Ok(api::IncomingWebhookEvent::PaymentIntentFailure) - } - EventType::Unknown - | EventType::Authorized - | EventType::Cancelled - | EventType::Refused - | EventType::Refunded - | EventType::SentForRefund - | EventType::CaptureFailed - | EventType::RefundFailed => Ok(api::IncomingWebhookEvent::EventNotSupported), - } - } - - fn get_webhook_resource_object( - &self, - request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult, errors::ConnectorError> { - let body: WorldpayWebhookEventType = request - .body - .parse_struct("WorldpayWebhookEventType") - .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?; - let psync_body = WorldpayEventResponse::try_from(body)?; - Ok(Box::new(psync_body)) - } -} diff --git a/crates/router/src/connector/worldpay/response.rs b/crates/router/src/connector/worldpay/response.rs deleted file mode 100644 index 14ccbc1fac87..000000000000 --- a/crates/router/src/connector/worldpay/response.rs +++ /dev/null @@ -1,350 +0,0 @@ -use masking::Secret; -use serde::{Deserialize, Serialize}; - -use crate::{core::errors, types, types::transformers::ForeignTryFrom}; -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct WorldpayPaymentsResponse { - #[serde(skip_serializing_if = "Option::is_none")] - pub exemption: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub issuer: Option, - pub outcome: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub payment_instrument: Option, - /// Any risk factors which have been identified for the authorization. This section will not appear if no risks are identified. - #[serde(skip_serializing_if = "Option::is_none")] - pub risk_factors: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub scheme: Option, - #[serde(rename = "_links", skip_serializing_if = "Option::is_none")] - pub links: Option, -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub enum Outcome { - Authorized, - Refused, -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct WorldpayEventResponse { - pub last_event: EventType, - #[serde(rename = "_links", skip_serializing_if = "Option::is_none")] - pub links: Option, -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub enum EventType { - Authorized, - Cancelled, - Charged, - SentForRefund, - RefundFailed, - Refused, - Refunded, - Error, - SentForSettlement, - Expired, - CaptureFailed, - #[serde(other)] - Unknown, -} - -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -pub struct Exemption { - pub result: String, - pub reason: String, -} - -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -pub struct PaymentLinks { - #[serde(rename = "payments:events", skip_serializing_if = "Option::is_none")] - pub events: Option, -} - -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -pub struct EventLinks { - #[serde(rename = "payments:events", skip_serializing_if = "Option::is_none")] - pub events: Option, -} - -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -pub struct PaymentLink { - pub href: String, -} - -fn get_resource_id( - links: Option, - transform_fn: F, -) -> Result> -where - F: Fn(String) -> T, -{ - let reference_id = links - .and_then(|l| l.events) - .and_then(|e| e.href.rsplit_once('/').map(|h| h.1.to_string())) - .map(transform_fn); - reference_id.ok_or_else(|| { - errors::ConnectorError::MissingRequiredField { - field_name: "links.events", - } - .into() - }) -} - -pub struct ResponseIdStr { - pub id: String, -} - -impl TryFrom> for ResponseIdStr { - type Error = error_stack::Report; - fn try_from(links: Option) -> Result { - get_resource_id(links, |id| Self { id }) - } -} - -impl ForeignTryFrom> for types::ResponseId { - type Error = error_stack::Report; - fn foreign_try_from(links: Option) -> Result { - get_resource_id(links, Self::ConnectorTransactionId) - } -} - -impl Exemption { - pub fn new(result: String, reason: String) -> Self { - Self { result, reason } - } -} - -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Issuer { - pub authorization_code: Secret, -} - -impl Issuer { - pub fn new(code: String) -> Self { - Self { - authorization_code: Secret::new(code), - } - } -} - -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -pub struct PaymentsResPaymentInstrument { - #[serde(rename = "type", skip_serializing_if = "Option::is_none")] - pub risk_type: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub card: Option, -} - -impl PaymentsResPaymentInstrument { - pub fn new() -> Self { - Self { - risk_type: None, - card: None, - } - } -} - -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct PaymentInstrumentCard { - #[serde(skip_serializing_if = "Option::is_none")] - pub number: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub issuer: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub payment_account_reference: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub country_code: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub funding_type: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub brand: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub expiry_date: Option, -} - -impl PaymentInstrumentCard { - pub fn new() -> Self { - Self { - number: None, - issuer: None, - payment_account_reference: None, - country_code: None, - funding_type: None, - brand: None, - expiry_date: None, - } - } -} - -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct PaymentInstrumentCardExpiryDate { - #[serde(skip_serializing_if = "Option::is_none")] - pub month: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub year: Option>, -} - -impl PaymentInstrumentCardExpiryDate { - pub fn new() -> Self { - Self { - month: None, - year: None, - } - } -} - -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct PaymentInstrumentCardIssuer { - #[serde(skip_serializing_if = "Option::is_none")] - pub name: Option, -} - -impl PaymentInstrumentCardIssuer { - pub fn new() -> Self { - Self { name: None } - } -} - -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct PaymentInstrumentCardNumber { - #[serde(skip_serializing_if = "Option::is_none")] - pub bin: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub last4_digits: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub dpan: Option, -} - -impl PaymentInstrumentCardNumber { - pub fn new() -> Self { - Self { - bin: None, - last4_digits: None, - dpan: None, - } - } -} - -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RiskFactorsInner { - #[serde(rename = "type")] - pub risk_type: RiskType, - #[serde(skip_serializing_if = "Option::is_none")] - pub detail: Option, - pub risk: Risk, -} - -impl RiskFactorsInner { - pub fn new(risk_type: RiskType, risk: Risk) -> Self { - Self { - risk_type, - detail: None, - risk, - } - } -} - -#[derive( - Clone, Copy, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, -)] -#[serde(rename_all = "camelCase")] -pub enum RiskType { - #[default] - Avs, - Cvc, - RiskProfile, -} - -#[derive( - Clone, Copy, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, -)] -#[serde(rename_all = "camelCase")] -pub enum Detail { - #[default] - Address, - Postcode, -} - -#[derive( - Clone, Copy, Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize, -)] -pub enum Risk { - #[default] - #[serde(rename = "not_checked")] - NotChecked, - #[serde(rename = "not_matched")] - NotMatched, - #[serde(rename = "not_supplied")] - NotSupplied, - #[serde(rename = "verificationFailed")] - VerificationFailed, -} - -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] -pub struct PaymentsResponseScheme { - pub reference: String, -} - -impl PaymentsResponseScheme { - pub fn new(reference: String) -> Self { - Self { reference } - } -} - -#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct WorldpayErrorResponse { - pub error_name: String, - pub message: String, - pub validation_errors: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct WorldpayWebhookTransactionId { - pub event_details: EventDetails, -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct EventDetails { - pub transaction_reference: String, - #[serde(rename = "type")] - pub event_type: EventType, -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct WorldpayWebhookEventType { - pub event_id: String, - pub event_timestamp: String, - pub event_details: EventDetails, -} - -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] -pub enum WorldpayWebhookStatus { - SentForSettlement, - Authorized, - SentForAuthorization, - Cancelled, - Error, - Expired, - Refused, - SentForRefund, - RefundFailed, -} diff --git a/crates/router/src/connector/worldpay/transformers.rs b/crates/router/src/connector/worldpay/transformers.rs deleted file mode 100644 index de82e11548ca..000000000000 --- a/crates/router/src/connector/worldpay/transformers.rs +++ /dev/null @@ -1,295 +0,0 @@ -use base64::Engine; -use common_utils::errors::CustomResult; -use diesel_models::enums; -use masking::{PeekInterface, Secret}; -use serde::Serialize; - -use super::{requests::*, response::*}; -use crate::{ - connector::utils, - consts, - core::errors, - types::{ - self, domain, transformers::ForeignTryFrom, PaymentsAuthorizeData, PaymentsResponseData, - }, -}; - -#[derive(Debug, Serialize)] -pub struct WorldpayRouterData { - amount: i64, - router_data: T, -} -impl - TryFrom<( - &types::api::CurrencyUnit, - types::storage::enums::Currency, - i64, - T, - )> for WorldpayRouterData -{ - type Error = error_stack::Report; - fn try_from( - (_currency_unit, _currency, amount, item): ( - &types::api::CurrencyUnit, - types::storage::enums::Currency, - i64, - T, - ), - ) -> Result { - Ok(Self { - amount, - router_data: item, - }) - } -} -fn fetch_payment_instrument( - payment_method: domain::PaymentMethodData, -) -> CustomResult { - match payment_method { - domain::PaymentMethodData::Card(card) => Ok(PaymentInstrument::Card(CardPayment { - card_expiry_date: CardExpiryDate { - month: utils::CardData::get_expiry_month_as_i8(&card)?, - year: utils::CardData::get_expiry_year_as_i32(&card)?, - }, - card_number: card.card_number, - ..CardPayment::default() - })), - domain::PaymentMethodData::Wallet(wallet) => match wallet { - domain::WalletData::GooglePay(data) => { - Ok(PaymentInstrument::Googlepay(WalletPayment { - payment_type: PaymentType::Googlepay, - wallet_token: Secret::new(data.tokenization_data.token), - ..WalletPayment::default() - })) - } - domain::WalletData::ApplePay(data) => Ok(PaymentInstrument::Applepay(WalletPayment { - payment_type: PaymentType::Applepay, - wallet_token: Secret::new(data.payment_data), - ..WalletPayment::default() - })), - domain::WalletData::AliPayQr(_) - | domain::WalletData::AliPayRedirect(_) - | domain::WalletData::AliPayHkRedirect(_) - | domain::WalletData::MomoRedirect(_) - | domain::WalletData::KakaoPayRedirect(_) - | domain::WalletData::GoPayRedirect(_) - | domain::WalletData::GcashRedirect(_) - | domain::WalletData::ApplePayRedirect(_) - | domain::WalletData::ApplePayThirdPartySdk(_) - | domain::WalletData::DanaRedirect {} - | domain::WalletData::GooglePayRedirect(_) - | domain::WalletData::GooglePayThirdPartySdk(_) - | domain::WalletData::MbWayRedirect(_) - | domain::WalletData::MobilePayRedirect(_) - | domain::WalletData::PaypalRedirect(_) - | domain::WalletData::PaypalSdk(_) - | domain::WalletData::SamsungPay(_) - | domain::WalletData::TwintRedirect {} - | domain::WalletData::VippsRedirect {} - | domain::WalletData::TouchNGoRedirect(_) - | domain::WalletData::WeChatPayRedirect(_) - | domain::WalletData::CashappQr(_) - | domain::WalletData::SwishQr(_) - | domain::WalletData::WeChatPayQr(_) - | domain::WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("worldpay"), - ) - .into()), - }, - domain::PaymentMethodData::PayLater(_) - | domain::PaymentMethodData::BankRedirect(_) - | domain::PaymentMethodData::BankDebit(_) - | domain::PaymentMethodData::BankTransfer(_) - | domain::PaymentMethodData::Crypto(_) - | domain::PaymentMethodData::MandatePayment - | domain::PaymentMethodData::Reward - | domain::PaymentMethodData::RealTimePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::CardRedirect(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) => { - Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("worldpay"), - ) - .into()) - } - } -} - -impl - TryFrom< - &WorldpayRouterData< - &types::RouterData< - types::api::payments::Authorize, - PaymentsAuthorizeData, - PaymentsResponseData, - >, - >, - > for WorldpayPaymentsRequest -{ - type Error = error_stack::Report; - - fn try_from( - item: &WorldpayRouterData< - &types::RouterData< - types::api::payments::Authorize, - PaymentsAuthorizeData, - PaymentsResponseData, - >, - >, - ) -> Result { - Ok(Self { - instruction: Instruction { - value: PaymentValue { - amount: item.amount, - currency: item.router_data.request.currency.to_string(), - }, - narrative: InstructionNarrative { - line1: item - .router_data - .merchant_id - .get_string_repr() - .replace('_', "-"), - ..Default::default() - }, - payment_instrument: fetch_payment_instrument( - item.router_data.request.payment_method_data.clone(), - )?, - debt_repayment: None, - }, - merchant: Merchant { - entity: item - .router_data - .connector_request_reference_id - .clone() - .replace('_', "-"), - ..Default::default() - }, - transaction_reference: item.router_data.connector_request_reference_id.clone(), - channel: None, - customer: None, - }) - } -} - -pub struct WorldpayAuthType { - pub(super) api_key: Secret, -} - -impl TryFrom<&types::ConnectorAuthType> for WorldpayAuthType { - type Error = error_stack::Report; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { - match auth_type { - types::ConnectorAuthType::BodyKey { api_key, key1 } => { - let auth_key = format!("{}:{}", key1.peek(), api_key.peek()); - let auth_header = format!("Basic {}", consts::BASE64_ENGINE.encode(auth_key)); - Ok(Self { - api_key: Secret::new(auth_header), - }) - } - _ => Err(errors::ConnectorError::FailedToObtainAuthType)?, - } - } -} - -impl From for enums::AttemptStatus { - fn from(item: Outcome) -> Self { - match item { - Outcome::Authorized => Self::Authorized, - Outcome::Refused => Self::Failure, - } - } -} - -impl From for enums::AttemptStatus { - fn from(value: EventType) -> Self { - match value { - EventType::Authorized => Self::Authorized, - EventType::CaptureFailed => Self::CaptureFailed, - EventType::Refused => Self::Failure, - EventType::Charged | EventType::SentForSettlement => Self::Charged, - EventType::Cancelled - | EventType::SentForRefund - | EventType::RefundFailed - | EventType::Refunded - | EventType::Error - | EventType::Expired - | EventType::Unknown => Self::Pending, - } - } -} - -impl From for enums::RefundStatus { - fn from(value: EventType) -> Self { - match value { - EventType::Refunded => Self::Success, - EventType::RefundFailed => Self::Failure, - EventType::Authorized - | EventType::Cancelled - | EventType::Charged - | EventType::SentForRefund - | EventType::Refused - | EventType::Error - | EventType::SentForSettlement - | EventType::Expired - | EventType::CaptureFailed - | EventType::Unknown => Self::Pending, - } - } -} - -impl TryFrom> - for types::PaymentsAuthorizeRouterData -{ - type Error = error_stack::Report; - fn try_from( - item: types::PaymentsResponseRouterData, - ) -> Result { - Ok(Self { - status: match item.response.outcome { - Some(outcome) => enums::AttemptStatus::from(outcome), - None => Err(errors::ConnectorError::MissingRequiredField { - field_name: "outcome", - })?, - }, - description: item.response.description, - response: Ok(PaymentsResponseData::TransactionResponse { - 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 - }) - } -} - -impl TryFrom<&types::RefundsRouterData> for WorldpayRefundRequest { - type Error = error_stack::Report; - fn try_from(item: &types::RefundsRouterData) -> Result { - Ok(Self { - reference: item.request.connector_transaction_id.clone(), - value: PaymentValue { - amount: item.request.refund_amount, - currency: item.request.currency.to_string(), - }, - }) - } -} - -impl TryFrom for WorldpayEventResponse { - type Error = error_stack::Report; - fn try_from(event: WorldpayWebhookEventType) -> Result { - Ok(Self { - last_event: event.event_details.event_type, - links: None, - }) - } -} diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs index 9e70cb2b96b0..e2784b968ad1 100644 --- a/crates/router/src/consts.rs +++ b/crates/router/src/consts.rs @@ -2,6 +2,9 @@ pub mod opensearch; #[cfg(feature = "olap")] pub mod user; pub mod user_role; + +use std::collections::HashSet; + use common_utils::consts; pub use hyperswitch_interfaces::consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE}; // ID generation @@ -49,15 +52,8 @@ pub(crate) const DEFAULT_NOTIFICATION_SCRIPT_LANGUAGE: &str = "en-US"; pub(crate) const BASE64_ENGINE: base64::engine::GeneralPurpose = consts::BASE64_ENGINE; -pub(crate) const BASE64_ENGINE_URL_SAFE: base64::engine::GeneralPurpose = - base64::engine::general_purpose::URL_SAFE; - pub(crate) const API_KEY_LENGTH: usize = 64; -// Apple Pay validation url -pub(crate) const APPLEPAY_VALIDATION_URL: &str = - "https://apple-pay-gateway-cert.apple.com/paymentservices/startSession"; - // OID (Object Identifier) for the merchant ID field extension. pub(crate) const MERCHANT_ID_FIELD_EXTENSION_ID: &str = "1.2.840.113635.100.6.32"; @@ -86,6 +82,11 @@ pub const EMAIL_TOKEN_TIME_IN_SECS: u64 = 60 * 60 * 24; // 1 day #[cfg(feature = "email")] pub const EMAIL_TOKEN_BLACKLIST_PREFIX: &str = "BET_"; +pub const EMAIL_SUBJECT_API_KEY_EXPIRY: &str = "API Key Expiry Notice"; +pub const EMAIL_SUBJECT_DASHBOARD_FEATURE_REQUEST: &str = "Dashboard Pro Feature Request by"; +pub const EMAIL_SUBJECT_APPROVAL_RECON_REQUEST: &str = + "Approval of Recon Request - Access Granted to Recon Dashboard"; + pub const ROLE_INFO_CACHE_PREFIX: &str = "CR_INFO_"; #[cfg(feature = "olap")] @@ -130,24 +131,82 @@ pub const CONNECTOR_CREDS_TOKEN_TTL: i64 = 900; pub const MAX_ALLOWED_AMOUNT: i64 = 999999999; //payment attempt default unified error code and unified error message -pub const DEFAULT_UNIFIED_ERROR_CODE: &str = "UE_000"; +pub const DEFAULT_UNIFIED_ERROR_CODE: &str = "UE_9000"; pub const DEFAULT_UNIFIED_ERROR_MESSAGE: &str = "Something went wrong"; // Recon's feature tag pub const RECON_FEATURE_TAG: &str = "RECONCILIATION AND SETTLEMENT"; +// Length of the unique reference ID generated for connector mandate requests +pub const CONNECTOR_MANDATE_REQUEST_REFERENCE_ID_LENGTH: usize = 18; + +/// Default allowed domains for payment links +pub const DEFAULT_ALLOWED_DOMAINS: Option> = None; + +/// Default hide card nickname field +pub const DEFAULT_HIDE_CARD_NICKNAME_FIELD: bool = false; + +/// Show card form by default for payment links +pub const DEFAULT_SHOW_CARD_FORM: bool = true; + +/// Default bool for Display sdk only +pub const DEFAULT_DISPLAY_SDK_ONLY: bool = false; + +/// Default bool to enable saved payment method +pub const DEFAULT_ENABLE_SAVED_PAYMENT_METHOD: bool = false; + +/// Default Merchant Logo Link +pub const DEFAULT_MERCHANT_LOGO: &str = + "https://live.hyperswitch.io/payment-link-assets/Merchant_placeholder.png"; + +/// Default Payment Link Background color +pub const DEFAULT_BACKGROUND_COLOR: &str = "#212E46"; + +/// Default product Img Link +pub const DEFAULT_PRODUCT_IMG: &str = + "https://live.hyperswitch.io/payment-link-assets/cart_placeholder.png"; + +/// Default SDK Layout +pub const DEFAULT_SDK_LAYOUT: &str = "tabs"; + /// Vault Add request url #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -pub const ADD_VAULT_REQUEST_URL: &str = "/vault/add"; +pub const ADD_VAULT_REQUEST_URL: &str = "/api/v2/vault/add"; /// Vault Get Fingerprint request url #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -pub const VAULT_FINGERPRINT_REQUEST_URL: &str = "/fingerprint"; +pub const VAULT_FINGERPRINT_REQUEST_URL: &str = "/api/v2/vault/fingerprint"; /// Vault Retrieve request url #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -pub const VAULT_RETRIEVE_REQUEST_URL: &str = "/vault/retrieve"; +pub const VAULT_RETRIEVE_REQUEST_URL: &str = "/api/v2/vault/retrieve"; + +/// Vault Delete request url +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub const VAULT_DELETE_REQUEST_URL: &str = "/api/v2/vault/delete"; /// Vault Header content type #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] pub const VAULT_HEADER_CONTENT_TYPE: &str = "application/json"; + +/// Vault Add flow type +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub const VAULT_ADD_FLOW_TYPE: &str = "add_to_vault"; + +/// Vault Retrieve flow type +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub const VAULT_RETRIEVE_FLOW_TYPE: &str = "retrieve_from_vault"; + +/// Vault Delete flow type +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub const VAULT_DELETE_FLOW_TYPE: &str = "delete_from_vault"; + +/// Vault Fingerprint fetch flow type +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub const VAULT_GET_FINGERPRINT_FLOW_TYPE: &str = "get_fingerprint_vault"; + +/// Max volume split for Dynamic routing +pub const DYNAMIC_ROUTING_MAX_VOLUME: u8 = 100; + +/// Click To Pay +pub const CLICK_TO_PAY: &str = "click_to_pay"; diff --git a/crates/router/src/consts/opensearch.rs b/crates/router/src/consts/opensearch.rs index fc1c071439a6..c9eeec1b343a 100644 --- a/crates/router/src/consts/opensearch.rs +++ b/crates/router/src/consts/opensearch.rs @@ -1,22 +1,16 @@ use api_models::analytics::search::SearchIndex; -use crate::services::authorization::permissions::Permission; - -pub const OPENSEARCH_INDEX_PERMISSIONS: &[(SearchIndex, &[Permission])] = &[ - ( +pub const fn get_search_indexes() -> [SearchIndex; 8] { + [ SearchIndex::PaymentAttempts, - &[Permission::PaymentRead, Permission::PaymentWrite], - ), - ( SearchIndex::PaymentIntents, - &[Permission::PaymentRead, Permission::PaymentWrite], - ), - ( SearchIndex::Refunds, - &[Permission::RefundRead, Permission::RefundWrite], - ), - ( SearchIndex::Disputes, - &[Permission::DisputeRead, Permission::DisputeWrite], - ), -]; + SearchIndex::SessionizerPaymentAttempts, + SearchIndex::SessionizerPaymentIntents, + SearchIndex::SessionizerRefunds, + SearchIndex::SessionizerDisputes, + ] +} + +pub const SEARCH_INDEXES: [SearchIndex; 8] = get_search_indexes(); diff --git a/crates/router/src/consts/user.rs b/crates/router/src/consts/user.rs index 6ac2b08e6bb7..d4eb12e39bf9 100644 --- a/crates/router/src/consts/user.rs +++ b/crates/router/src/consts/user.rs @@ -15,6 +15,12 @@ pub const TOTP_DIGITS: usize = 6; pub const TOTP_VALIDITY_DURATION_IN_SECONDS: u64 = 30; /// Number of totps allowed as network delay. 1 would mean one totp before current totp and one totp after are valids. pub const TOTP_TOLERANCE: u8 = 1; +/// Number of maximum attempts user has for totp +pub const TOTP_MAX_ATTEMPTS: u8 = 4; +/// Number of maximum attempts user has for recovery code +pub const RECOVERY_CODE_MAX_ATTEMPTS: u8 = 4; +/// The default number of organizations to fetch for a tenant-level user +pub const ORG_LIST_LIMIT_FOR_TENANT: u32 = 20; pub const MAX_PASSWORD_LENGTH: usize = 70; pub const MIN_PASSWORD_LENGTH: usize = 8; @@ -23,6 +29,19 @@ pub const REDIS_TOTP_PREFIX: &str = "TOTP_"; pub const REDIS_RECOVERY_CODE_PREFIX: &str = "RC_"; pub const REDIS_TOTP_SECRET_PREFIX: &str = "TOTP_SEC_"; pub const REDIS_TOTP_SECRET_TTL_IN_SECS: i64 = 15 * 60; // 15 minutes +pub const REDIS_TOTP_ATTEMPTS_PREFIX: &str = "TOTP_ATTEMPTS_"; +pub const REDIS_RECOVERY_CODE_ATTEMPTS_PREFIX: &str = "RC_ATTEMPTS_"; +pub const REDIS_TOTP_ATTEMPTS_TTL_IN_SECS: i64 = 5 * 60; // 5 mins +pub const REDIS_RECOVERY_CODE_ATTEMPTS_TTL_IN_SECS: i64 = 10 * 60; // 10 mins pub const REDIS_SSO_PREFIX: &str = "SSO_"; pub const REDIS_SSO_TTL: i64 = 5 * 60; // 5 minutes + +/// Email subject +pub const EMAIL_SUBJECT_SIGNUP: &str = "Welcome to the Hyperswitch community!"; +pub const EMAIL_SUBJECT_INVITATION: &str = "You have been invited to join Hyperswitch Community!"; +pub const EMAIL_SUBJECT_MAGIC_LINK: &str = "Unlock Hyperswitch: Use Your Magic Link to Sign In"; +pub const EMAIL_SUBJECT_RESET_PASSWORD: &str = "Get back to Hyperswitch - Reset Your Password Now"; +pub const EMAIL_SUBJECT_NEW_PROD_INTENT: &str = "New Prod Intent"; +pub const EMAIL_SUBJECT_WELCOME_TO_COMMUNITY: &str = + "Thank you for signing up on Hyperswitch Dashboard!"; diff --git a/crates/router/src/core.rs b/crates/router/src/core.rs index e2ccca462984..c22cecc20f9f 100644 --- a/crates/router/src/core.rs +++ b/crates/router/src/core.rs @@ -51,5 +51,8 @@ pub mod utils; pub mod verification; #[cfg(feature = "olap")] pub mod verify_connector; -#[cfg(feature = "v1")] pub mod webhooks; + +pub mod unified_authentication_service; + +pub mod relay; diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index d27b6b82fd42..24a2b25d6ce9 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -8,16 +8,18 @@ use common_utils::{ date_time, ext_traits::{AsyncExt, Encode, OptionExt, ValueExt}, id_type, pii, type_name, - types::keymanager::{self as km_types, KeyManagerState}, + types::keymanager::{self as km_types, KeyManagerState, ToEncryptable}, }; use diesel_models::configs; #[cfg(all(any(feature = "v1", feature = "v2"), feature = "olap"))] use diesel_models::organization::OrganizationBridge; use error_stack::{report, FutureExt, ResultExt}; +use hyperswitch_domain_models::merchant_connector_account::{ + FromRequestEncryptableMerchantConnectorAccount, UpdateEncryptableMerchantConnectorAccount, +}; use masking::{ExposeInterface, PeekInterface, Secret}; use pm_auth::{connector::plaid::transformers::PlaidAuthType, types as pm_auth_types}; use regex::Regex; -use router_env::metrics::add_attributes; use uuid::Uuid; #[cfg(any(feature = "v1", feature = "v2"))] @@ -194,7 +196,7 @@ pub async fn create_merchant_account( let master_key = db.get_master_key(); - let key_manager_state = &(&state).into(); + let key_manager_state: &KeyManagerState = &(&state).into(); let merchant_id = req.get_merchant_reference_id(); let identifier = km_types::Identifier::Merchant(merchant_id.clone()); #[cfg(feature = "keymanager_create")] @@ -203,16 +205,18 @@ pub async fn create_merchant_account( use crate::consts::BASE64_ENGINE; - keymanager::transfer_key_to_key_manager( - key_manager_state, - EncryptionTransferRequest { - identifier: identifier.clone(), - key: BASE64_ENGINE.encode(key), - }, - ) - .await - .change_context(errors::ApiErrorResponse::DuplicateMerchantAccount) - .attach_printable("Failed to insert key to KeyManager")?; + if key_manager_state.enabled { + keymanager::transfer_key_to_key_manager( + key_manager_state, + EncryptionTransferRequest { + identifier: identifier.clone(), + key: BASE64_ENGINE.encode(key), + }, + ) + .await + .change_context(errors::ApiErrorResponse::DuplicateMerchantAccount) + .attach_printable("Failed to insert key to KeyManager")?; + } } let key_store = domain::MerchantKeyStore { @@ -398,6 +402,7 @@ impl MerchantAccountCreateBridge for api::MerchantAccountCreate { payment_link_config: None, pm_collect_link_config, version: hyperswitch_domain_models::consts::API_VERSION, + is_platform_account: false, }, ) } @@ -665,6 +670,7 @@ impl MerchantAccountCreateBridge for api::MerchantAccountCreate { modified_at: date_time::now(), organization_id: organization.get_organization_id(), recon_status: diesel_models::enums::ReconStatus::NotRequested, + is_platform_account: false, }), ) } @@ -1194,7 +1200,7 @@ struct ConnectorAuthTypeAndMetadataValidation<'a> { connector_meta_data: &'a Option, } -impl<'a> ConnectorAuthTypeAndMetadataValidation<'a> { +impl ConnectorAuthTypeAndMetadataValidation<'_> { pub fn validate_auth_and_metadata_type( &self, ) -> Result<(), error_stack::Report> { @@ -1315,6 +1321,7 @@ impl<'a> ConnectorAuthTypeAndMetadataValidation<'a> { cryptopay::transformers::CryptopayAuthType::try_from(self.auth_type)?; Ok(()) } + api_enums::Connector::CtpMastercard => Ok(()), api_enums::Connector::Cybersource => { cybersource::transformers::CybersourceAuthType::try_from(self.auth_type)?; cybersource::transformers::CybersourceConnectorMetadataObject::try_from( @@ -1330,6 +1337,10 @@ impl<'a> ConnectorAuthTypeAndMetadataValidation<'a> { deutschebank::transformers::DeutschebankAuthType::try_from(self.auth_type)?; Ok(()) } + api_enums::Connector::Digitalvirgo => { + digitalvirgo::transformers::DigitalvirgoAuthType::try_from(self.auth_type)?; + Ok(()) + } api_enums::Connector::Dlocal => { dlocal::transformers::DlocalAuthType::try_from(self.auth_type)?; Ok(()) @@ -1338,6 +1349,10 @@ impl<'a> ConnectorAuthTypeAndMetadataValidation<'a> { ebanx::transformers::EbanxAuthType::try_from(self.auth_type)?; Ok(()) } + api_enums::Connector::Elavon => { + elavon::transformers::ElavonAuthType::try_from(self.auth_type)?; + Ok(()) + } api_enums::Connector::Fiserv => { fiserv::transformers::FiservAuthType::try_from(self.auth_type)?; fiserv::transformers::FiservSessionObject::try_from(self.connector_meta_data)?; @@ -1380,10 +1395,18 @@ impl<'a> ConnectorAuthTypeAndMetadataValidation<'a> { iatapay::transformers::IatapayAuthType::try_from(self.auth_type)?; Ok(()) } + // api_enums::Connector::Inespay => { + // inespay::transformers::InespayAuthType::try_from(self.auth_type)?; + // Ok(()) + // } api_enums::Connector::Itaubank => { itaubank::transformers::ItaubankAuthType::try_from(self.auth_type)?; Ok(()) } + api_enums::Connector::Jpmorgan => { + jpmorgan::transformers::JpmorganAuthType::try_from(self.auth_type)?; + Ok(()) + } api_enums::Connector::Klarna => { klarna::transformers::KlarnaAuthType::try_from(self.auth_type)?; klarna::transformers::KlarnaConnectorMetadataObject::try_from( @@ -1415,6 +1438,10 @@ impl<'a> ConnectorAuthTypeAndMetadataValidation<'a> { nexinets::transformers::NexinetsAuthType::try_from(self.auth_type)?; Ok(()) } + api_enums::Connector::Nexixpay => { + nexixpay::transformers::NexixpayAuthType::try_from(self.auth_type)?; + Ok(()) + } api_enums::Connector::Nmi => { nmi::transformers::NmiAuthType::try_from(self.auth_type)?; Ok(()) @@ -1555,7 +1582,7 @@ struct ConnectorAuthTypeValidation<'a> { auth_type: &'a types::ConnectorAuthType, } -impl<'a> ConnectorAuthTypeValidation<'a> { +impl ConnectorAuthTypeValidation<'_> { fn validate_connector_auth_type( &self, ) -> Result<(), error_stack::Report> { @@ -1645,7 +1672,7 @@ struct ConnectorStatusAndDisabledValidation<'a> { current_status: &'a api_enums::ConnectorStatus, } -impl<'a> ConnectorStatusAndDisabledValidation<'a> { +impl ConnectorStatusAndDisabledValidation<'_> { fn validate_status_and_disabled( &self, ) -> RouterResult<(api_enums::ConnectorStatus, Option)> { @@ -1677,44 +1704,19 @@ impl<'a> ConnectorStatusAndDisabledValidation<'a> { } (Some(disabled), _) => Some(*disabled), (None, common_enums::ConnectorStatus::Inactive) => Some(true), - (None, _) => None, + // Enable the connector if nothing is passed in the request + (None, _) => Some(false), }; Ok((*connector_status, disabled)) } } -struct PaymentMethodsEnabled<'a> { - payment_methods_enabled: &'a Option>, -} - -impl<'a> PaymentMethodsEnabled<'a> { - fn get_payment_methods_enabled(&self) -> RouterResult>> { - let mut vec = Vec::new(); - let payment_methods_enabled = match self.payment_methods_enabled.clone() { - Some(val) => { - for pm in val.into_iter() { - let pm_value = pm - .encode_to_value() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "Failed while encoding to serde_json::Value, PaymentMethod", - )?; - vec.push(Secret::new(pm_value)) - } - Some(vec) - } - None => None, - }; - Ok(payment_methods_enabled) - } -} - struct ConnectorMetadata<'a> { connector_metadata: &'a Option, } -impl<'a> ConnectorMetadata<'a> { +impl ConnectorMetadata<'_> { fn validate_apple_pay_certificates_in_mca_metadata(&self) -> RouterResult<()> { self.connector_metadata .clone() @@ -1805,7 +1807,7 @@ struct ConnectorTypeAndConnectorName<'a> { connector_name: &'a api_enums::Connector, } -impl<'a> ConnectorTypeAndConnectorName<'a> { +impl ConnectorTypeAndConnectorName<'_> { fn get_routable_connector(&self) -> RouterResult> { let mut routable_connector = api_enums::RoutableConnectors::from_str(&self.connector_name.to_string()).ok(); @@ -1910,6 +1912,65 @@ impl<'a> MerchantDefaultConfigUpdate<'a> { } Ok(()) } + + async fn retrieve_and_delete_from_default_fallback_routing_algorithm_if_routable_connector_exists( + &self, + ) -> RouterResult<()> { + let mut default_routing_config = routing::helpers::get_merchant_default_config( + self.store, + self.merchant_id.get_string_repr(), + self.transaction_type, + ) + .await?; + + let mut default_routing_config_for_profile = routing::helpers::get_merchant_default_config( + self.store, + self.profile_id.get_string_repr(), + self.transaction_type, + ) + .await?; + + if let Some(routable_connector_val) = self.routable_connector { + let choice = routing_types::RoutableConnectorChoice { + choice_kind: routing_types::RoutableChoiceKind::FullStruct, + connector: *routable_connector_val, + merchant_connector_id: Some(self.merchant_connector_id.clone()), + }; + if default_routing_config.contains(&choice) { + default_routing_config.retain(|mca| { + mca.merchant_connector_id + .as_ref() + .map_or(true, |merchant_connector_id| { + merchant_connector_id != self.merchant_connector_id + }) + }); + routing::helpers::update_merchant_default_config( + self.store, + self.merchant_id.get_string_repr(), + default_routing_config.clone(), + self.transaction_type, + ) + .await?; + } + if default_routing_config_for_profile.contains(&choice.clone()) { + default_routing_config_for_profile.retain(|mca| { + mca.merchant_connector_id + .as_ref() + .map_or(true, |merchant_connector_id| { + merchant_connector_id != self.merchant_connector_id + }) + }); + routing::helpers::update_merchant_default_config( + self.store, + self.profile_id.get_string_repr(), + default_routing_config_for_profile.clone(), + self.transaction_type, + ) + .await?; + } + } + Ok(()) + } } #[cfg(feature = "v2")] struct DefaultFallbackRoutingConfigUpdate<'a> { @@ -1949,6 +2010,40 @@ impl<'a> DefaultFallbackRoutingConfigUpdate<'a> { } Ok(()) } + + async fn retrieve_and_delete_from_default_fallback_routing_algorithm_if_routable_connector_exists( + &self, + ) -> RouterResult<()> { + let profile_wrapper = ProfileWrapper::new(self.business_profile.clone()); + let default_routing_config_for_profile = + &mut profile_wrapper.get_default_fallback_list_of_connector_under_profile()?; + if let Some(routable_connector_val) = self.routable_connector { + let choice = routing_types::RoutableConnectorChoice { + choice_kind: routing_types::RoutableChoiceKind::FullStruct, + connector: *routable_connector_val, + merchant_connector_id: Some(self.merchant_connector_id.clone()), + }; + if default_routing_config_for_profile.contains(&choice.clone()) { + default_routing_config_for_profile.retain(|mca| { + mca.merchant_connector_id + .as_ref() + .map_or(true, |merchant_connector_id| { + merchant_connector_id != self.merchant_connector_id + }) + }); + + profile_wrapper + .update_default_fallback_routing_of_connectors_under_profile( + self.store, + default_routing_config_for_profile, + self.key_manager_state, + &self.key_store, + ) + .await? + } + } + Ok(()) + } } #[cfg(any(feature = "v1", feature = "v2", feature = "olap"))] #[async_trait::async_trait] @@ -2000,13 +2095,10 @@ impl MerchantConnectorAccountUpdateBridge for api_models::admin::MerchantConnect key_manager_state: &KeyManagerState, merchant_account: &domain::MerchantAccount, ) -> RouterResult { - let payment_methods_enabled = PaymentMethodsEnabled { - payment_methods_enabled: &self.payment_methods_enabled, - }; - let payment_methods_enabled = payment_methods_enabled.get_payment_methods_enabled()?; - let frm_configs = self.get_frm_config_as_secret(); + let payment_methods_enabled = self.payment_methods_enabled; + let auth = types::ConnectorAuthType::from_secret_value( self.connector_account_details .clone() @@ -2078,25 +2170,40 @@ impl MerchantConnectorAccountUpdateBridge for api_models::admin::MerchantConnect .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to serialize MerchantRecipientData")?; + let encrypted_data = domain_types::crypto_operation( + key_manager_state, + type_name!(domain::MerchantConnectorAccount), + domain_types::CryptoOperation::BatchEncrypt( + UpdateEncryptableMerchantConnectorAccount::to_encryptable( + UpdateEncryptableMerchantConnectorAccount { + connector_account_details: self.connector_account_details, + connector_wallets_details: + helpers::get_connector_wallets_details_with_apple_pay_certificates( + &self.metadata, + &self.connector_wallets_details, + ) + .await?, + additional_merchant_data: merchant_recipient_data.map(Secret::new), + }, + ), + ), + km_types::Identifier::Merchant(key_store.merchant_id.clone()), + key_store.key.peek(), + ) + .await + .and_then(|val| val.try_into_batchoperation()) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while decrypting connector account details".to_string())?; + + let encrypted_data = + UpdateEncryptableMerchantConnectorAccount::from_encryptable(encrypted_data) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while decrypting connector account details")?; + Ok(storage::MerchantConnectorAccountUpdate::Update { connector_type: Some(self.connector_type), connector_label: self.connector_label.clone(), - connector_account_details: self - .connector_account_details - .async_lift(|inner| async { - domain_types::crypto_operation( - key_manager_state, - type_name!(storage::MerchantConnectorAccount), - domain_types::CryptoOperation::EncryptOptional(inner), - km_types::Identifier::Merchant(key_store.merchant_id.clone()), - key_store.key.get_inner().peek(), - ) - .await - .and_then(|val| val.try_into_optionaloperation()) - }) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed while encrypting data")?, + connector_account_details: Box::new(encrypted_data.connector_account_details), disabled, payment_methods_enabled, metadata: self.metadata, @@ -2110,33 +2217,10 @@ impl MerchantConnectorAccountUpdateBridge for api_models::admin::MerchantConnect None => None, }, applepay_verified_domains: None, - pm_auth_config: self.pm_auth_config, + pm_auth_config: Box::new(self.pm_auth_config), status: Some(connector_status), - additional_merchant_data: if let Some(mcd) = merchant_recipient_data { - Some( - domain_types::crypto_operation( - key_manager_state, - type_name!(domain::MerchantConnectorAccount), - domain_types::CryptoOperation::Encrypt(Secret::new(mcd)), - km_types::Identifier::Merchant(key_store.merchant_id.clone()), - key_store.key.peek(), - ) - .await - .and_then(|val| val.try_into_operation()) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to encrypt additional_merchant_data")?, - ) - } else { - None - }, - connector_wallets_details: - helpers::get_encrypted_connector_wallets_details_with_apple_pay_certificates( - state, - &key_store, - &metadata, - &self.connector_wallets_details, - ) - .await?, + additional_merchant_data: Box::new(encrypted_data.additional_merchant_data), + connector_wallets_details: Box::new(encrypted_data.connector_wallets_details), }) } } @@ -2256,68 +2340,62 @@ impl MerchantConnectorAccountUpdateBridge for api_models::admin::MerchantConnect .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to serialize MerchantRecipientData")?; + let encrypted_data = domain_types::crypto_operation( + key_manager_state, + type_name!(domain::MerchantConnectorAccount), + domain_types::CryptoOperation::BatchEncrypt( + UpdateEncryptableMerchantConnectorAccount::to_encryptable( + UpdateEncryptableMerchantConnectorAccount { + connector_account_details: self.connector_account_details, + connector_wallets_details: + helpers::get_connector_wallets_details_with_apple_pay_certificates( + &self.metadata, + &self.connector_wallets_details, + ) + .await?, + additional_merchant_data: merchant_recipient_data.map(Secret::new), + }, + ), + ), + km_types::Identifier::Merchant(key_store.merchant_id.clone()), + key_store.key.peek(), + ) + .await + .and_then(|val| val.try_into_batchoperation()) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while decrypting connector account details".to_string())?; + + let encrypted_data = + UpdateEncryptableMerchantConnectorAccount::from_encryptable(encrypted_data) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while decrypting connector account details")?; + Ok(storage::MerchantConnectorAccountUpdate::Update { connector_type: Some(self.connector_type), connector_name: None, merchant_connector_id: None, connector_label: self.connector_label.clone(), - connector_account_details: self - .connector_account_details - .async_lift(|inner| async { - domain_types::crypto_operation( - key_manager_state, - type_name!(storage::MerchantConnectorAccount), - domain_types::CryptoOperation::EncryptOptional(inner), - km_types::Identifier::Merchant(key_store.merchant_id.clone()), - key_store.key.get_inner().peek(), - ) - .await - .and_then(|val| val.try_into_optionaloperation()) - }) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed while encrypting data")?, + connector_account_details: Box::new(encrypted_data.connector_account_details), test_mode: self.test_mode, disabled, payment_methods_enabled, metadata: self.metadata, frm_configs, connector_webhook_details: match &self.connector_webhook_details { - Some(connector_webhook_details) => connector_webhook_details - .encode_to_value() - .change_context(errors::ApiErrorResponse::InternalServerError) - .map(Some)? - .map(Secret::new), - None => None, + Some(connector_webhook_details) => Box::new( + connector_webhook_details + .encode_to_value() + .change_context(errors::ApiErrorResponse::InternalServerError) + .map(Some)? + .map(Secret::new), + ), + None => Box::new(None), }, applepay_verified_domains: None, - pm_auth_config: self.pm_auth_config, + pm_auth_config: Box::new(self.pm_auth_config), status: Some(connector_status), - additional_merchant_data: if let Some(mcd) = merchant_recipient_data { - Some( - domain_types::crypto_operation( - key_manager_state, - type_name!(domain::MerchantConnectorAccount), - domain_types::CryptoOperation::Encrypt(Secret::new(mcd)), - km_types::Identifier::Merchant(key_store.merchant_id.clone()), - key_store.key.peek(), - ) - .await - .and_then(|val| val.try_into_operation()) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to encrypt additional_merchant_data")?, - ) - } else { - None - }, - connector_wallets_details: - helpers::get_encrypted_connector_wallets_details_with_apple_pay_certificates( - state, - &key_store, - &metadata, - &self.connector_wallets_details, - ) - .await?, + additional_merchant_data: Box::new(encrypted_data.additional_merchant_data), + connector_wallets_details: Box::new(encrypted_data.connector_wallets_details), }) } } @@ -2354,11 +2432,8 @@ impl MerchantConnectorAccountCreateBridge for api::MerchantConnectorCreate { ) -> RouterResult { // If connector label is not passed in the request, generate one let connector_label = self.get_connector_label(business_profile.profile_name.clone()); - let payment_methods_enabled = PaymentMethodsEnabled { - payment_methods_enabled: &self.payment_methods_enabled, - }; - let payment_methods_enabled = payment_methods_enabled.get_payment_methods_enabled()?; let frm_configs = self.get_frm_config_as_secret(); + let payment_methods_enabled = self.payment_methods_enabled; // Validate Merchant api details and return error if not in correct format let auth = types::ConnectorAuthType::from_option_secret_value( self.connector_account_details.clone(), @@ -2382,6 +2457,7 @@ impl MerchantConnectorAccountCreateBridge for api::MerchantConnectorCreate { }; let (connector_status, disabled) = connector_status_and_disabled_validation.validate_status_and_disabled()?; + let identifier = km_types::Identifier::Merchant(business_profile.merchant_id.clone()); let merchant_recipient_data = if let Some(data) = &self.additional_merchant_data { Some( @@ -2406,25 +2482,46 @@ impl MerchantConnectorAccountCreateBridge for api::MerchantConnectorCreate { .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to serialize MerchantRecipientData")?; + + let encrypted_data = domain_types::crypto_operation( + key_manager_state, + type_name!(domain::MerchantConnectorAccount), + domain_types::CryptoOperation::BatchEncrypt( + FromRequestEncryptableMerchantConnectorAccount::to_encryptable( + FromRequestEncryptableMerchantConnectorAccount { + connector_account_details: self.connector_account_details.ok_or( + errors::ApiErrorResponse::MissingRequiredField { + field_name: "connector_account_details", + }, + )?, + connector_wallets_details: + helpers::get_connector_wallets_details_with_apple_pay_certificates( + &self.metadata, + &self.connector_wallets_details, + ) + .await?, + additional_merchant_data: merchant_recipient_data.map(Secret::new), + }, + ), + ), + identifier.clone(), + key_store.key.peek(), + ) + .await + .and_then(|val| val.try_into_batchoperation()) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while decrypting connector account details".to_string())?; + + let encrypted_data = + FromRequestEncryptableMerchantConnectorAccount::from_encryptable(encrypted_data) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while decrypting connector account details")?; + Ok(domain::MerchantConnectorAccount { merchant_id: business_profile.merchant_id.clone(), connector_type: self.connector_type, connector_name: self.connector_name.to_string(), - connector_account_details: domain_types::crypto_operation( - key_manager_state, - type_name!(domain::MerchantConnectorAccount), - domain_types::CryptoOperation::Encrypt(self.connector_account_details.ok_or( - errors::ApiErrorResponse::MissingRequiredField { - field_name: "connector_account_details", - }, - )?), - identifier.clone(), - key_store.key.peek(), - ) - .await - .and_then(|val| val.try_into_operation()) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to encrypt connector account details")?, + connector_account_details: encrypted_data.connector_account_details, payment_methods_enabled, disabled, metadata: self.metadata.clone(), @@ -2448,22 +2545,8 @@ impl MerchantConnectorAccountCreateBridge for api::MerchantConnectorCreate { applepay_verified_domains: None, pm_auth_config: self.pm_auth_config.clone(), status: connector_status, - connector_wallets_details: helpers::get_encrypted_connector_wallets_details_with_apple_pay_certificates(state, &key_store, &self.metadata, &self.connector_wallets_details).await?, - additional_merchant_data: if let Some(mcd) = merchant_recipient_data { - Some(domain_types::crypto_operation( - key_manager_state, - type_name!(domain::MerchantConnectorAccount), - domain_types::CryptoOperation::Encrypt(Secret::new(mcd)), - km_types::Identifier::Merchant(key_store.merchant_id.clone()), - key_store.key.peek(), - ) - .await - .and_then(|val| val.try_into_operation()) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to encrypt additional_merchant_data")?) - } else { - None - }, + connector_wallets_details: encrypted_data.connector_wallets_details, + additional_merchant_data: encrypted_data.additional_merchant_data, version: hyperswitch_domain_models::consts::API_VERSION, }) } @@ -2495,6 +2578,34 @@ impl MerchantConnectorAccountCreateBridge for api::MerchantConnectorCreate { } } +#[cfg(feature = "v1")] +struct PaymentMethodsEnabled<'a> { + payment_methods_enabled: &'a Option>, +} + +#[cfg(feature = "v1")] +impl PaymentMethodsEnabled<'_> { + fn get_payment_methods_enabled(&self) -> RouterResult>> { + let mut vec = Vec::new(); + let payment_methods_enabled = match self.payment_methods_enabled.clone() { + Some(val) => { + for pm in val.into_iter() { + let pm_value = pm + .encode_to_value() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "Failed while encoding to serde_json::Value, PaymentMethod", + )?; + vec.push(Secret::new(pm_value)) + } + Some(vec) + } + None => None, + }; + Ok(payment_methods_enabled) + } +} + #[cfg(all(feature = "v1", feature = "olap"))] #[async_trait::async_trait] impl MerchantConnectorAccountCreateBridge for api::MerchantConnectorCreate { @@ -2571,26 +2682,47 @@ impl MerchantConnectorAccountCreateBridge for api::MerchantConnectorCreate { .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to serialize MerchantRecipientData")?; + + let encrypted_data = domain_types::crypto_operation( + key_manager_state, + type_name!(domain::MerchantConnectorAccount), + domain_types::CryptoOperation::BatchEncrypt( + FromRequestEncryptableMerchantConnectorAccount::to_encryptable( + FromRequestEncryptableMerchantConnectorAccount { + connector_account_details: self.connector_account_details.ok_or( + errors::ApiErrorResponse::MissingRequiredField { + field_name: "connector_account_details", + }, + )?, + connector_wallets_details: + helpers::get_connector_wallets_details_with_apple_pay_certificates( + &self.metadata, + &self.connector_wallets_details, + ) + .await?, + additional_merchant_data: merchant_recipient_data.map(Secret::new), + }, + ), + ), + identifier.clone(), + key_store.key.peek(), + ) + .await + .and_then(|val| val.try_into_batchoperation()) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while decrypting connector account details".to_string())?; + + let encrypted_data = + FromRequestEncryptableMerchantConnectorAccount::from_encryptable(encrypted_data) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while decrypting connector account details")?; + Ok(domain::MerchantConnectorAccount { merchant_id: business_profile.merchant_id.clone(), connector_type: self.connector_type, connector_name: self.connector_name.to_string(), merchant_connector_id: common_utils::generate_merchant_connector_account_id_of_default_length(), - connector_account_details: domain_types::crypto_operation( - key_manager_state, - type_name!(domain::MerchantConnectorAccount), - domain_types::CryptoOperation::Encrypt(self.connector_account_details.ok_or( - errors::ApiErrorResponse::MissingRequiredField { - field_name: "connector_account_details", - }, - )?), - identifier.clone(), - key_store.key.peek(), - ) - .await - .and_then(|val| val.try_into_operation()) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to encrypt connector account details")?, + connector_account_details: encrypted_data.connector_account_details, payment_methods_enabled, disabled, metadata: self.metadata.clone(), @@ -2613,26 +2745,12 @@ impl MerchantConnectorAccountCreateBridge for api::MerchantConnectorCreate { applepay_verified_domains: None, pm_auth_config: self.pm_auth_config.clone(), status: connector_status, - connector_wallets_details: helpers::get_encrypted_connector_wallets_details_with_apple_pay_certificates(state, &key_store, &self.metadata, &self.connector_wallets_details).await?, + connector_wallets_details: encrypted_data.connector_wallets_details, test_mode: self.test_mode, business_country: self.business_country, business_label: self.business_label.clone(), business_sub_label: self.business_sub_label.clone(), - additional_merchant_data: if let Some(mcd) = merchant_recipient_data { - Some(domain_types::crypto_operation( - key_manager_state, - type_name!(domain::MerchantConnectorAccount), - domain_types::CryptoOperation::Encrypt(Secret::new(mcd)), - identifier, - key_store.key.peek(), - ) - .await - .and_then(|val| val.try_into_operation()) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to encrypt additional_merchant_data")?) - } else { - None - }, + additional_merchant_data: encrypted_data.additional_merchant_data, version: hyperswitch_domain_models::consts::API_VERSION, }) } @@ -2702,7 +2820,6 @@ pub async fn create_connector( let key_manager_state = &(&state).into(); #[cfg(feature = "dummy_connector")] req.connector_name - .clone() .validate_dummy_connector_enabled(state.conf.dummy_connector.enabled) .change_context(errors::ApiErrorResponse::InvalidRequestData { message: "Invalid connector name".to_string(), @@ -2814,12 +2931,11 @@ pub async fn create_connector( .await?; metrics::MCA_CREATE.add( - &metrics::CONTEXT, 1, - &add_attributes([ + router_env::metric_attributes!( ("connector", req.connector_name.to_string()), - ("merchant", merchant_id.get_string_repr().to_owned()), - ]), + ("merchant", merchant_id.clone()), + ), ); let mca_response = mca.foreign_try_into()?; @@ -2955,19 +3071,11 @@ pub async fn retrieve_connector( #[cfg(all(feature = "olap", feature = "v2"))] pub async fn list_connectors_for_a_profile( state: SessionState, - merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, profile_id: id_type::ProfileId, ) -> RouterResponse> { let store = state.store.as_ref(); let key_manager_state = &(&state).into(); - let key_store = store - .get_merchant_key_store_by_merchant_id( - key_manager_state, - merchant_account.get_id(), - &store.get_master_key().to_vec().into(), - ) - .await - .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; let merchant_connector_accounts = store .list_connector_account_by_profile_id(key_manager_state, &profile_id, &key_store) @@ -3126,7 +3234,7 @@ pub async fn delete_connector( .await .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; - let _mca = db + let mca = db .find_by_merchant_connector_account_merchant_id_merchant_connector_id( key_manager_state, &merchant_id, @@ -3148,6 +3256,26 @@ pub async fn delete_connector( id: merchant_connector_id.get_string_repr().to_string(), })?; + // delete the mca from the config as well + let merchant_default_config_delete = MerchantDefaultConfigUpdate { + routable_connector: &Some( + common_enums::RoutableConnectors::from_str(&mca.connector_name).map_err(|_| { + errors::ApiErrorResponse::InvalidDataValue { + field_name: "connector_name", + } + })?, + ), + merchant_connector_id: &mca.get_id(), + store: db, + merchant_id: &merchant_id, + profile_id: &mca.profile_id, + transaction_type: &mca.connector_type.into(), + }; + + merchant_default_config_delete + .retrieve_and_delete_from_default_fallback_routing_algorithm_if_routable_connector_exists() + .await?; + let response = api::MerchantConnectorDeleteResponse { merchant_id, merchant_connector_id, @@ -3194,6 +3322,32 @@ pub async fn delete_connector( id: id.clone().get_string_repr().to_string(), })?; + let business_profile = db + .find_business_profile_by_profile_id(key_manager_state, &key_store, &mca.profile_id) + .await + .to_not_found_response(errors::ApiErrorResponse::ProfileNotFound { + id: mca.profile_id.get_string_repr().to_owned(), + })?; + + let merchant_default_config_delete = DefaultFallbackRoutingConfigUpdate { + routable_connector: &Some( + common_enums::RoutableConnectors::from_str(&mca.connector_name).map_err(|_| { + errors::ApiErrorResponse::InvalidDataValue { + field_name: "connector_name", + } + })?, + ), + merchant_connector_id: &mca.get_id(), + store: db, + business_profile, + key_store, + key_manager_state, + }; + + merchant_default_config_delete + .retrieve_and_delete_from_default_fallback_routing_algorithm_if_routable_connector_exists() + .await?; + let response = api::MerchantConnectorDeleteResponse { merchant_id: merchant_id.clone(), id, @@ -3450,9 +3604,12 @@ impl ProfileCreateBridge for api::ProfileCreate { .unwrap_or(common_utils::crypto::generate_cryptographically_secure_random_string(64)); let payment_link_config = self.payment_link_config.map(ForeignInto::foreign_into); + let key_manager_state = state.into(); let outgoing_webhook_custom_http_headers = self .outgoing_webhook_custom_http_headers - .async_map(|headers| cards::create_encrypted_data(state, key_store, headers)) + .async_map(|headers| { + cards::create_encrypted_data(&key_manager_state, key_store, headers) + }) .await .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) @@ -3470,6 +3627,13 @@ impl ProfileCreateBridge for api::ProfileCreate { }) .transpose()?; + let authentication_product_ids = self + .authentication_product_ids + .map(serde_json::to_value) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("failed to parse product authentication id's to value")?; + Ok(domain::Profile::from(domain::ProfileSetter { profile_id, merchant_id: merchant_account.get_id().clone(), @@ -3539,6 +3703,8 @@ impl ProfileCreateBridge for api::ProfileCreate { is_network_tokenization_enabled: self.is_network_tokenization_enabled, is_auto_retries_enabled: self.is_auto_retries_enabled.unwrap_or_default(), max_auto_retries_enabled: self.max_auto_retries_enabled.map(i16::from), + is_click_to_pay_enabled: self.is_click_to_pay_enabled, + authentication_product_ids, })) } @@ -3567,9 +3733,12 @@ impl ProfileCreateBridge for api::ProfileCreate { .unwrap_or(common_utils::crypto::generate_cryptographically_secure_random_string(64)); let payment_link_config = self.payment_link_config.map(ForeignInto::foreign_into); + let key_manager_state = state.into(); let outgoing_webhook_custom_http_headers = self .outgoing_webhook_custom_http_headers - .async_map(|headers| cards::create_encrypted_data(state, key_store, headers)) + .async_map(|headers| { + cards::create_encrypted_data(&key_manager_state, key_store, headers) + }) .await .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) @@ -3587,13 +3756,20 @@ impl ProfileCreateBridge for api::ProfileCreate { }) .transpose()?; + let authentication_product_ids = self + .authentication_product_ids + .map(serde_json::to_value) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("failed to parse product authentication id's to value")?; + Ok(domain::Profile::from(domain::ProfileSetter { id: profile_id, merchant_id: merchant_id.clone(), profile_name, created_at: current_time, modified_at: current_time, - return_url: self.return_url.map(|return_url| return_url.to_string()), + return_url: self.return_url, enable_payment_response_hash: self.enable_payment_response_hash.unwrap_or(true), payment_response_hash_key: Some(payment_response_hash_key), redirect_to_merchant_with_http_post: self @@ -3639,9 +3815,12 @@ impl ProfileCreateBridge for api::ProfileCreate { .or(Some(common_utils::consts::DEFAULT_ORDER_FULFILLMENT_TIME)), order_fulfillment_time_origin: self.order_fulfillment_time_origin, default_fallback_routing: None, + should_collect_cvv_during_payment: false, tax_connector_id: self.tax_connector_id, is_tax_connector_enabled: self.is_tax_connector_enabled, is_network_tokenization_enabled: self.is_network_tokenization_enabled, + is_click_to_pay_enabled: self.is_click_to_pay_enabled, + authentication_product_ids, })) } } @@ -3828,9 +4007,12 @@ impl ProfileUpdateBridge for api::ProfileUpdate { }) .transpose()? .map(Secret::new); + let key_manager_state = state.into(); let outgoing_webhook_custom_http_headers = self .outgoing_webhook_custom_http_headers - .async_map(|headers| cards::create_encrypted_data(state, key_store, headers)) + .async_map(|headers| { + cards::create_encrypted_data(&key_manager_state, key_store, headers) + }) .await .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) @@ -3846,6 +4028,13 @@ impl ProfileUpdateBridge for api::ProfileUpdate { }) .transpose()?; + let authentication_product_ids = self + .authentication_product_ids + .map(serde_json::to_value) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("failed to parse product authentication id's to value")?; + Ok(domain::ProfileUpdate::Update(Box::new( domain::ProfileGeneralUpdate { profile_name: self.profile_name, @@ -3888,6 +4077,8 @@ impl ProfileUpdateBridge for api::ProfileUpdate { is_network_tokenization_enabled: self.is_network_tokenization_enabled, is_auto_retries_enabled: self.is_auto_retries_enabled, max_auto_retries_enabled: self.max_auto_retries_enabled.map(i16::from), + is_click_to_pay_enabled: self.is_click_to_pay_enabled, + authentication_product_ids, }, ))) } @@ -3929,9 +4120,12 @@ impl ProfileUpdateBridge for api::ProfileUpdate { }) .transpose()? .map(Secret::new); + let key_manager_state = state.into(); let outgoing_webhook_custom_http_headers = self .outgoing_webhook_custom_http_headers - .async_map(|headers| cards::create_encrypted_data(state, key_store, headers)) + .async_map(|headers| { + cards::create_encrypted_data(&key_manager_state, key_store, headers) + }) .await .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) @@ -3947,10 +4141,17 @@ impl ProfileUpdateBridge for api::ProfileUpdate { }) .transpose()?; + let authentication_product_ids = self + .authentication_product_ids + .map(serde_json::to_value) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("failed to parse product authentication id's to value")?; + Ok(domain::ProfileUpdate::Update(Box::new( domain::ProfileGeneralUpdate { profile_name: self.profile_name, - return_url: self.return_url.map(|return_url| return_url.to_string()), + return_url: self.return_url, enable_payment_response_hash: self.enable_payment_response_hash, payment_response_hash_key: self.payment_response_hash_key, redirect_to_merchant_with_http_post: self.redirect_to_merchant_with_http_post, @@ -3981,6 +4182,8 @@ impl ProfileUpdateBridge for api::ProfileUpdate { always_collect_shipping_details_from_wallet_connector: self .always_collect_shipping_details_from_wallet_connector, is_network_tokenization_enabled: self.is_network_tokenization_enabled, + is_click_to_pay_enabled: self.is_click_to_pay_enabled, + authentication_product_ids, }, ))) } @@ -4196,7 +4399,7 @@ pub async fn extended_card_info_toggle( .is_some_and(|existing_config| existing_config != ext_card_info_choice.enabled) { let profile_update = domain::ProfileUpdate::ExtendedCardInfoUpdate { - is_extended_card_info_enabled: Some(ext_card_info_choice.enabled), + is_extended_card_info_enabled: ext_card_info_choice.enabled, }; db.update_profile_by_profile_id( @@ -4250,7 +4453,7 @@ pub async fn connector_agnostic_mit_toggle( != Some(connector_agnostic_mit_choice.enabled) { let profile_update = domain::ProfileUpdate::ConnectorAgnosticMitUpdate { - is_connector_agnostic_mit_enabled: Some(connector_agnostic_mit_choice.enabled), + is_connector_agnostic_mit_enabled: connector_agnostic_mit_choice.enabled, }; db.update_profile_by_profile_id( @@ -4566,3 +4769,35 @@ async fn locker_recipient_create_call( Ok(store_resp.card_reference) } + +pub async fn enable_platform_account( + state: SessionState, + merchant_id: id_type::MerchantId, +) -> RouterResponse<()> { + let db = state.store.as_ref(); + let key_manager_state = &(&state).into(); + let key_store = db + .get_merchant_key_store_by_merchant_id( + key_manager_state, + &merchant_id, + &db.get_master_key().to_vec().into(), + ) + .await + .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; + + let merchant_account = db + .find_merchant_account_by_merchant_id(key_manager_state, &merchant_id, &key_store) + .await + .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; + + db.update_merchant( + key_manager_state, + merchant_account, + storage::MerchantAccountUpdate::ToPlatformAccount, + &key_store, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error while enabling platform merchant account") + .map(|_| services::ApplicationResponse::StatusOk) +} diff --git a/crates/router/src/core/api_keys.rs b/crates/router/src/core/api_keys.rs index 6e907dbd91bf..4d3f3df9a59c 100644 --- a/crates/router/src/core/api_keys.rs +++ b/crates/router/src/core/api_keys.rs @@ -3,7 +3,7 @@ use common_utils::date_time; use diesel_models::{api_keys::ApiKey, enums as storage_enums}; use error_stack::{report, ResultExt}; use masking::{PeekInterface, StrongSecret}; -use router_env::{instrument, metrics::add_attributes, tracing}; +use router_env::{instrument, tracing}; use crate::{ configs::settings, @@ -13,7 +13,6 @@ use crate::{ routes::{metrics, SessionState}, services::{authentication, ApplicationResponse}, types::{api, storage, transformers::ForeignInto}, - utils, }; #[cfg(feature = "email")] @@ -63,9 +62,9 @@ impl PlaintextApiKey { Self(format!("{env}_{key}").into()) } - pub fn new_key_id() -> String { + pub fn new_key_id() -> common_utils::id_type::ApiKeyId { let env = router_env::env::prefix_for_env(); - utils::generate_id(consts::ID_LENGTH, env) + common_utils::id_type::ApiKeyId::generate_key_id(env) } pub fn prefix(&self) -> String { @@ -161,9 +160,8 @@ pub async fn create_api_key( ); metrics::API_KEY_CREATED.add( - &metrics::CONTEXT, 1, - &add_attributes([("merchant", merchant_id.get_string_repr().to_owned())]), + router_env::metric_attributes!(("merchant", merchant_id.clone())), ); // Add process to process_tracker for email reminder, only if expiry is set to future date @@ -223,7 +221,7 @@ pub async fn add_api_key_expiry_task( expiry_reminder_days: expiry_reminder_days.clone(), }; - let process_tracker_id = generate_task_id_for_api_key_expiry_workflow(api_key.key_id.as_str()); + let process_tracker_id = generate_task_id_for_api_key_expiry_workflow(&api_key.key_id); let process_tracker_entry = storage::ProcessTrackerNew::new( process_tracker_id, API_KEY_EXPIRY_NAME, @@ -241,15 +239,11 @@ pub async fn add_api_key_expiry_task( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable_lazy(|| { format!( - "Failed while inserting API key expiry reminder to process_tracker: api_key_id: {}", + "Failed while inserting API key expiry reminder to process_tracker: {:?}", api_key.key_id ) })?; - metrics::TASKS_ADDED_COUNT.add( - &metrics::CONTEXT, - 1, - &add_attributes([("flow", "ApiKeyExpiry")]), - ); + metrics::TASKS_ADDED_COUNT.add(1, router_env::metric_attributes!(("flow", "ApiKeyExpiry"))); Ok(()) } @@ -258,11 +252,11 @@ pub async fn add_api_key_expiry_task( pub async fn retrieve_api_key( state: SessionState, merchant_id: common_utils::id_type::MerchantId, - key_id: &str, + key_id: common_utils::id_type::ApiKeyId, ) -> RouterResponse { let store = state.store.as_ref(); let api_key = store - .find_api_key_by_merchant_id_key_id_optional(&merchant_id, key_id) + .find_api_key_by_merchant_id_key_id_optional(&merchant_id, &key_id) .await .change_context(errors::ApiErrorResponse::InternalServerError) // If retrieve failed .attach_printable("Failed to retrieve API key")? @@ -388,7 +382,7 @@ pub async fn update_api_key_expiry_task( } } - let task_id = generate_task_id_for_api_key_expiry_workflow(api_key.key_id.as_str()); + let task_id = generate_task_id_for_api_key_expiry_workflow(&api_key.key_id); let task_ids = vec![task_id.clone()]; @@ -430,7 +424,7 @@ pub async fn update_api_key_expiry_task( pub async fn revoke_api_key( state: SessionState, merchant_id: &common_utils::id_type::MerchantId, - key_id: &str, + key_id: &common_utils::id_type::ApiKeyId, ) -> RouterResponse { let store = state.store.as_ref(); @@ -457,7 +451,7 @@ pub async fn revoke_api_key( ); } - metrics::API_KEY_REVOKED.add(&metrics::CONTEXT, 1, &[]); + metrics::API_KEY_REVOKED.add(1, &[]); #[cfg(feature = "email")] { @@ -496,7 +490,7 @@ pub async fn revoke_api_key( #[instrument(skip_all)] pub async fn revoke_api_key_expiry_task( store: &dyn crate::db::StorageInterface, - key_id: &str, + key_id: &common_utils::id_type::ApiKeyId, ) -> Result<(), errors::ProcessTrackerError> { let task_id = generate_task_id_for_api_key_expiry_workflow(key_id); let task_ids = vec![task_id]; @@ -535,8 +529,13 @@ pub async fn list_api_keys( } #[cfg(feature = "email")] -fn generate_task_id_for_api_key_expiry_workflow(key_id: &str) -> String { - format!("{API_KEY_EXPIRY_RUNNER}_{API_KEY_EXPIRY_NAME}_{key_id}") +fn generate_task_id_for_api_key_expiry_workflow( + key_id: &common_utils::id_type::ApiKeyId, +) -> String { + format!( + "{API_KEY_EXPIRY_RUNNER}_{API_KEY_EXPIRY_NAME}_{}", + key_id.get_string_repr() + ) } impl From<&str> for PlaintextApiKey { diff --git a/crates/router/src/core/authentication.rs b/crates/router/src/core/authentication.rs index 29508b5c0f55..09cebda49088 100644 --- a/crates/router/src/core/authentication.rs +++ b/crates/router/src/core/authentication.rs @@ -24,8 +24,8 @@ pub async fn perform_authentication( authentication_connector: String, payment_method_data: domain::PaymentMethodData, payment_method: common_enums::PaymentMethod, - billing_address: payments::Address, - shipping_address: Option, + billing_address: hyperswitch_domain_models::address::Address, + shipping_address: Option, browser_details: Option, merchant_connector_account: payments_core::helpers::MerchantConnectorAccountType, amount: Option, @@ -39,6 +39,7 @@ pub async fn perform_authentication( email: Option, webhook_url: String, three_ds_requestor_url: String, + psd2_sca_exemption_type: Option, ) -> CustomResult { let router_data = transformers::construct_authentication_router_data( merchant_id, @@ -60,6 +61,7 @@ pub async fn perform_authentication( email, webhook_url, three_ds_requestor_url, + psd2_sca_exemption_type, )?; let response = Box::pin(utils::do_auth_connector_call( state, diff --git a/crates/router/src/core/authentication/transformers.rs b/crates/router/src/core/authentication/transformers.rs index 6013cdbe97f7..30373d1408f5 100644 --- a/crates/router/src/core/authentication/transformers.rs +++ b/crates/router/src/core/authentication/transformers.rs @@ -28,8 +28,8 @@ pub fn construct_authentication_router_data( authentication_connector: String, payment_method_data: domain::PaymentMethodData, payment_method: PaymentMethod, - billing_address: payments::Address, - shipping_address: Option, + billing_address: hyperswitch_domain_models::address::Address, + shipping_address: Option, browser_details: Option, amount: Option, currency: Option, @@ -43,6 +43,7 @@ pub fn construct_authentication_router_data( email: Option, webhook_url: String, three_ds_requestor_url: String, + psd2_sca_exemption_type: Option, ) -> RouterResult { let router_request = types::authentication::ConnectorAuthenticationRequestData { payment_method_data, @@ -70,6 +71,7 @@ pub fn construct_authentication_router_data( types::PaymentAddress::default(), router_request, &merchant_connector_account, + psd2_sca_exemption_type, ) } @@ -94,6 +96,7 @@ pub fn construct_post_authentication_router_data( types::PaymentAddress::default(), router_request, &merchant_connector_account, + None, ) } @@ -119,6 +122,7 @@ pub fn construct_pre_authentication_router_data( types::PaymentAddress::default(), router_request, merchant_connector_account, + None, ) } @@ -129,6 +133,7 @@ pub fn construct_router_data( address: types::PaymentAddress, request_data: Req, merchant_connector_account: &payments_helpers::MerchantConnectorAccountType, + psd2_sca_exemption_type: Option, ) -> RouterResult> { let test_mode: Option = merchant_connector_account.is_test_mode_on(); let auth_type: types::ConnectorAuthType = merchant_connector_account @@ -149,7 +154,6 @@ pub fn construct_router_data( payment_method, connector_auth_type: auth_type, description: None, - return_url: None, address, auth_type: common_enums::AuthenticationType::NoThreeDs, connector_meta_data: merchant_connector_account.get_metadata(), @@ -184,6 +188,9 @@ pub fn construct_router_data( integrity_check: Ok(()), additional_merchant_data: None, header_payload: None, + connector_mandate_request_reference_id: None, + authentication_id: None, + psd2_sca_exemption_type, }) } diff --git a/crates/router/src/core/authentication/utils.rs b/crates/router/src/core/authentication/utils.rs index e1e5dc9f6248..21c2ae20e2ca 100644 --- a/crates/router/src/core/authentication/utils.rs +++ b/crates/router/src/core/authentication/utils.rs @@ -17,6 +17,7 @@ use crate::{ utils::OptionExt, }; +#[cfg(feature = "v1")] pub fn get_connector_data_if_separate_authn_supported( connector_call_type: &api::ConnectorCallType, ) -> Option { @@ -218,6 +219,7 @@ pub async fn create_new_authentication( ds_trans_id: None, directory_server_id: None, acquirer_country_code: None, + service_details: None, }; state .store diff --git a/crates/router/src/core/connector_onboarding.rs b/crates/router/src/core/connector_onboarding.rs index c3de228d9a7b..5813a5bcd0cc 100644 --- a/crates/router/src/core/connector_onboarding.rs +++ b/crates/router/src/core/connector_onboarding.rs @@ -95,7 +95,7 @@ pub async fn sync_onboarding_status( .await?; return Ok(ApplicationResponse::Json(api::OnboardingStatus::PayPal( - api::PayPalOnboardingStatus::ConnectorIntegrated(update_mca_data), + api::PayPalOnboardingStatus::ConnectorIntegrated(Box::new(update_mca_data)), ))); } Ok(ApplicationResponse::Json(status)) diff --git a/crates/router/src/core/currency.rs b/crates/router/src/core/currency.rs index 96d75098271b..912484b014a7 100644 --- a/crates/router/src/core/currency.rs +++ b/crates/router/src/core/currency.rs @@ -1,4 +1,6 @@ +use analytics::errors::AnalyticsError; use common_utils::errors::CustomResult; +use currency_conversion::types::ExchangeRates; use error_stack::ResultExt; use crate::{ @@ -46,3 +48,19 @@ pub async fn convert_forex( .change_context(ApiErrorResponse::InternalServerError)?, )) } + +pub async fn get_forex_exchange_rates( + state: SessionState, +) -> CustomResult { + let forex_api = state.conf.forex_api.get_inner(); + let rates = get_forex_rates( + &state, + forex_api.call_delay, + forex_api.local_fetch_retry_delay, + forex_api.local_fetch_retry_count, + ) + .await + .change_context(AnalyticsError::ForexFetchFailed)?; + + Ok((*rates.data).clone()) +} diff --git a/crates/router/src/core/customers.rs b/crates/router/src/core/customers.rs index fc7d717d5f1a..65bbf736f1a7 100644 --- a/crates/router/src/core/customers.rs +++ b/crates/router/src/core/customers.rs @@ -1,16 +1,15 @@ -use api_models::customers::CustomerRequestWithEmail; use common_utils::{ crypto::Encryptable, errors::ReportSwitchExt, - ext_traits::{AsyncExt, OptionExt}, - id_type, type_name, + ext_traits::AsyncExt, + id_type, pii, type_name, types::{ keymanager::{Identifier, KeyManagerState, ToEncryptable}, Description, }, }; use error_stack::{report, ResultExt}; -use masking::{Secret, SwitchStrategy}; +use masking::{ExposeInterface, Secret, SwitchStrategy}; use router_env::{instrument, tracing}; #[cfg(all(feature = "v2", feature = "customer_v2"))] @@ -145,13 +144,15 @@ impl CustomerCreateBridge for customers::CustomerRequest { let encrypted_data = types::crypto_operation( key_manager_state, type_name!(domain::Customer), - types::CryptoOperation::BatchEncrypt(CustomerRequestWithEmail::to_encryptable( - CustomerRequestWithEmail { - name: self.name.clone(), - email: self.email.clone(), - phone: self.phone.clone(), - }, - )), + types::CryptoOperation::BatchEncrypt( + domain::FromRequestEncryptableCustomer::to_encryptable( + domain::FromRequestEncryptableCustomer { + name: self.name.clone(), + email: self.email.clone().map(|a| a.expose().switch_strategy()), + phone: self.phone.clone(), + }, + ), + ), Identifier::Merchant(key_store.merchant_id.clone()), key, ) @@ -160,8 +161,9 @@ impl CustomerCreateBridge for customers::CustomerRequest { .switch() .attach_printable("Failed while encrypting Customer")?; - let encryptable_customer = CustomerRequestWithEmail::from_encryptable(encrypted_data) - .change_context(errors::CustomersErrorResponse::InternalServerError)?; + let encryptable_customer = + domain::FromRequestEncryptableCustomer::from_encryptable(encrypted_data) + .change_context(errors::CustomersErrorResponse::InternalServerError)?; Ok(domain::Customer { customer_id: merchant_reference_id @@ -169,7 +171,13 @@ impl CustomerCreateBridge for customers::CustomerRequest { .ok_or(errors::CustomersErrorResponse::InternalServerError)?, merchant_id: merchant_id.to_owned(), name: encryptable_customer.name, - email: encryptable_customer.email, + email: encryptable_customer.email.map(|email| { + let encryptable: Encryptable> = Encryptable::new( + email.clone().into_inner().switch_strategy(), + email.into_encrypted(), + ); + encryptable + }), phone: encryptable_customer.phone, description: self.description.clone(), phone_country_code: self.phone_country_code.clone(), @@ -211,15 +219,18 @@ impl CustomerCreateBridge for customers::CustomerRequest { ) -> errors::CustomResult { let default_customer_billing_address = self.get_default_customer_billing_address(); let encrypted_customer_billing_address = default_customer_billing_address - .async_map(|billing_address| create_encrypted_data(state, key_store, billing_address)) + .async_map(|billing_address| { + create_encrypted_data(key_state, key_store, billing_address) + }) .await .transpose() .change_context(errors::CustomersErrorResponse::InternalServerError) .attach_printable("Unable to encrypt default customer billing address")?; let default_customer_shipping_address = self.get_default_customer_shipping_address(); - let encrypted_customer_shipping_address = default_customer_shipping_address - .async_map(|shipping_address| create_encrypted_data(state, key_store, shipping_address)) + .async_map(|shipping_address| { + create_encrypted_data(key_state, key_store, shipping_address) + }) .await .transpose() .change_context(errors::CustomersErrorResponse::InternalServerError) @@ -231,13 +242,15 @@ impl CustomerCreateBridge for customers::CustomerRequest { let encrypted_data = types::crypto_operation( key_state, type_name!(domain::Customer), - types::CryptoOperation::BatchEncrypt(CustomerRequestWithEmail::to_encryptable( - CustomerRequestWithEmail { - name: Some(self.name.clone()), - email: Some(self.email.clone()), - phone: self.phone.clone(), - }, - )), + types::CryptoOperation::BatchEncrypt( + domain::FromRequestEncryptableCustomer::to_encryptable( + domain::FromRequestEncryptableCustomer { + name: Some(self.name.clone()), + email: Some(self.email.clone().expose().switch_strategy()), + phone: self.phone.clone(), + }, + ), + ), Identifier::Merchant(key_store.merchant_id.clone()), key, ) @@ -246,15 +259,22 @@ impl CustomerCreateBridge for customers::CustomerRequest { .switch() .attach_printable("Failed while encrypting Customer")?; - let encryptable_customer = CustomerRequestWithEmail::from_encryptable(encrypted_data) - .change_context(errors::CustomersErrorResponse::InternalServerError)?; + let encryptable_customer = + domain::FromRequestEncryptableCustomer::from_encryptable(encrypted_data) + .change_context(errors::CustomersErrorResponse::InternalServerError)?; Ok(domain::Customer { - id: common_utils::generate_time_ordered_id("cus"), + id: id_type::GlobalCustomerId::generate(&state.conf.cell_information.id), merchant_reference_id: merchant_reference_id.to_owned(), merchant_id, name: encryptable_customer.name, - email: encryptable_customer.email, + email: encryptable_customer.email.map(|email| { + let encryptable: Encryptable> = Encryptable::new( + email.clone().into_inner().switch_strategy(), + email.into_encrypted(), + ); + encryptable + }), phone: encryptable_customer.phone, description: self.description.clone(), phone_country_code: self.phone_country_code.clone(), @@ -437,7 +457,7 @@ pub async fn retrieve_customer( merchant_account: domain::MerchantAccount, _profile_id: Option, key_store: domain::MerchantKeyStore, - req: customers::CustomerId, + customer_id: id_type::CustomerId, ) -> errors::CustomerResponse { let db = state.store.as_ref(); let key_manager_state = &(&state).into(); @@ -445,7 +465,7 @@ pub async fn retrieve_customer( let response = db .find_customer_by_customer_id_merchant_id( key_manager_state, - &req.customer_id, + &customer_id, merchant_account.get_id(), &key_store, merchant_account.storage_scheme, @@ -471,7 +491,7 @@ pub async fn retrieve_customer( state: SessionState, merchant_account: domain::MerchantAccount, key_store: domain::MerchantKeyStore, - req: customers::GlobalId, + id: id_type::GlobalCustomerId, ) -> errors::CustomerResponse { let db = state.store.as_ref(); let key_manager_state = &(&state).into(); @@ -479,7 +499,7 @@ pub async fn retrieve_customer( let response = db .find_customer_by_global_id( key_manager_state, - &req.id, + &id, merchant_account.get_id(), &key_store, merchant_account.storage_scheme, @@ -543,12 +563,12 @@ pub async fn list_customers( pub async fn delete_customer( state: SessionState, merchant_account: domain::MerchantAccount, - req: customers::GlobalId, + id: id_type::GlobalCustomerId, key_store: domain::MerchantKeyStore, ) -> errors::CustomerResponse { let db = &*state.store; let key_manager_state = &(&state).into(); - req.fetch_domain_model_and_update_and_generate_delete_customer_response( + id.redact_customer_details_and_generate_response( db, &key_store, &merchant_account, @@ -564,8 +584,8 @@ pub async fn delete_customer( feature = "payment_methods_v2" ))] #[async_trait::async_trait] -impl CustomerDeleteBridge for customers::GlobalId { - async fn fetch_domain_model_and_update_and_generate_delete_customer_response<'a>( +impl CustomerDeleteBridge for id_type::GlobalCustomerId { + async fn redact_customer_details_and_generate_response<'a>( &'a self, db: &'a dyn StorageInterface, key_store: &'a domain::MerchantKeyStore, @@ -576,7 +596,7 @@ impl CustomerDeleteBridge for customers::GlobalId { let customer_orig = db .find_customer_by_global_id( key_manager_state, - &self.id, + self, merchant_account.get_id(), key_store, merchant_account.storage_scheme, @@ -586,7 +606,7 @@ impl CustomerDeleteBridge for customers::GlobalId { let merchant_reference_id = customer_orig.merchant_reference_id.clone(); - let customer_mandates = db.find_mandate_by_global_id(&self.id).await.switch()?; + let customer_mandates = db.find_mandate_by_global_customer_id(self).await.switch()?; for mandate in customer_mandates.into_iter() { if mandate.mandate_status == enums::MandateStatus::Active { @@ -595,14 +615,19 @@ impl CustomerDeleteBridge for customers::GlobalId { } match db - .find_payment_method_list_by_global_id(key_manager_state, key_store, &self.id, None) + .find_payment_method_list_by_global_customer_id( + key_manager_state, + key_store, + self, + None, + ) .await { // check this in review Ok(customer_payment_methods) => { for pm in customer_payment_methods.into_iter() { - if pm.payment_method == Some(enums::PaymentMethod::Card) { - cards::delete_card_by_locker_id(state, &self.id, merchant_account.get_id()) + if pm.get_payment_method_type() == Some(enums::PaymentMethod::Card) { + cards::delete_card_by_locker_id(state, self, merchant_account.get_id()) .await .switch()?; } @@ -659,7 +684,7 @@ impl CustomerDeleteBridge for customers::GlobalId { description: Some(Description::from_str_unchecked(REDACTED)), phone_country_code: Some(REDACTED.to_string()), metadata: None, - connector_customer: None, + connector_customer: Box::new(None), default_billing_address: None, default_shipping_address: None, default_payment_method_id: None, @@ -668,7 +693,7 @@ impl CustomerDeleteBridge for customers::GlobalId { db.update_customer_by_global_id( key_manager_state, - self.id.clone(), + self, customer_orig, merchant_account.get_id(), updated_customer, @@ -679,20 +704,20 @@ impl CustomerDeleteBridge for customers::GlobalId { .switch()?; let response = customers::CustomerDeleteResponse { + id: self.clone(), merchant_reference_id, customer_deleted: true, address_deleted: true, payment_methods_deleted: true, - id: self.id.clone(), }; - metrics::CUSTOMER_REDACTED.add(&metrics::CONTEXT, 1, &[]); + metrics::CUSTOMER_REDACTED.add(1, &[]); Ok(services::ApplicationResponse::Json(response)) } } #[async_trait::async_trait] trait CustomerDeleteBridge { - async fn fetch_domain_model_and_update_and_generate_delete_customer_response<'a>( + async fn redact_customer_details_and_generate_response<'a>( &'a self, db: &'a dyn StorageInterface, key_store: &'a domain::MerchantKeyStore, @@ -711,19 +736,20 @@ trait CustomerDeleteBridge { pub async fn delete_customer( state: SessionState, merchant_account: domain::MerchantAccount, - req: customers::CustomerId, + customer_id: id_type::CustomerId, key_store: domain::MerchantKeyStore, ) -> errors::CustomerResponse { let db = &*state.store; let key_manager_state = &(&state).into(); - req.fetch_domain_model_and_update_and_generate_delete_customer_response( - db, - &key_store, - &merchant_account, - key_manager_state, - &state, - ) - .await + customer_id + .redact_customer_details_and_generate_response( + db, + &key_store, + &merchant_account, + key_manager_state, + &state, + ) + .await } #[cfg(all( @@ -732,8 +758,8 @@ pub async fn delete_customer( not(feature = "payment_methods_v2") ))] #[async_trait::async_trait] -impl CustomerDeleteBridge for customers::CustomerId { - async fn fetch_domain_model_and_update_and_generate_delete_customer_response<'a>( +impl CustomerDeleteBridge for id_type::CustomerId { + async fn redact_customer_details_and_generate_response<'a>( &'a self, db: &'a dyn StorageInterface, key_store: &'a domain::MerchantKeyStore, @@ -744,7 +770,7 @@ impl CustomerDeleteBridge for customers::CustomerId { let customer_orig = db .find_customer_by_customer_id_merchant_id( key_manager_state, - &self.customer_id, + self, merchant_account.get_id(), key_store, merchant_account.storage_scheme, @@ -753,7 +779,7 @@ impl CustomerDeleteBridge for customers::CustomerId { .switch()?; let customer_mandates = db - .find_mandate_by_merchant_id_customer_id(merchant_account.get_id(), &self.customer_id) + .find_mandate_by_merchant_id_customer_id(merchant_account.get_id(), self) .await .switch()?; @@ -767,7 +793,7 @@ impl CustomerDeleteBridge for customers::CustomerId { .find_payment_method_by_customer_id_merchant_id_list( key_manager_state, key_store, - &self.customer_id, + self, merchant_account.get_id(), None, ) @@ -776,10 +802,10 @@ impl CustomerDeleteBridge for customers::CustomerId { // check this in review Ok(customer_payment_methods) => { for pm in customer_payment_methods.into_iter() { - if pm.payment_method == Some(enums::PaymentMethod::Card) { + if pm.get_payment_method_type() == Some(enums::PaymentMethod::Card) { cards::delete_card_from_locker( state, - &self.customer_id, + self, merchant_account.get_id(), pm.locker_id.as_ref().unwrap_or(&pm.payment_method_id), ) @@ -790,7 +816,7 @@ impl CustomerDeleteBridge for customers::CustomerId { { network_tokenization::delete_network_token_from_locker_and_token_service( state, - &self.customer_id, + self, merchant_account.get_id(), pm.payment_method_id.clone(), pm.network_token_locker_id, @@ -864,7 +890,7 @@ impl CustomerDeleteBridge for customers::CustomerId { match db .update_address_by_merchant_id_customer_id( key_manager_state, - &self.customer_id, + self, merchant_account.get_id(), update_address, key_store, @@ -901,13 +927,13 @@ impl CustomerDeleteBridge for customers::CustomerId { description: Some(Description::from_str_unchecked(REDACTED)), phone_country_code: Some(REDACTED.to_string()), metadata: None, - connector_customer: None, + connector_customer: Box::new(None), address_id: None, }; db.update_customer_by_customer_id_merchant_id( key_manager_state, - self.customer_id.clone(), + self.clone(), merchant_account.get_id().to_owned(), customer_orig, updated_customer, @@ -918,12 +944,12 @@ impl CustomerDeleteBridge for customers::CustomerId { .switch()?; let response = customers::CustomerDeleteResponse { - customer_id: self.customer_id.clone(), + customer_id: self.clone(), customer_deleted: true, address_deleted: true, payment_methods_deleted: true, }; - metrics::CUSTOMER_REDACTED.add(&metrics::CONTEXT, 1, &[]); + metrics::CUSTOMER_REDACTED.add(1, &[]); Ok(services::ApplicationResponse::Json(response)) } } @@ -932,19 +958,24 @@ impl CustomerDeleteBridge for customers::CustomerId { pub async fn update_customer( state: SessionState, merchant_account: domain::MerchantAccount, - update_customer: customers::CustomerUpdateRequest, + update_customer: customers::CustomerUpdateRequestInternal, key_store: domain::MerchantKeyStore, - id: customers::UpdateCustomerId, ) -> errors::CustomerResponse { let db = state.store.as_ref(); let key_manager_state = &(&state).into(); //Add this in update call if customer can be updated anywhere else - let merchant_reference_id = update_customer.get_merchant_reference_id(); + #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] + let verify_id_for_update_customer = VerifyIdForUpdateCustomer { + merchant_reference_id: &update_customer.customer_id, + merchant_account: &merchant_account, + key_store: &key_store, + key_manager_state, + }; + #[cfg(all(feature = "v2", feature = "customer_v2"))] let verify_id_for_update_customer = VerifyIdForUpdateCustomer { - merchant_reference_id: merchant_reference_id.as_ref(), - id: &id, + id: &update_customer.id, merchant_account: &merchant_account, key_store: &key_store, key_manager_state, @@ -955,6 +986,7 @@ pub async fn update_customer( .await?; let updated_customer = update_customer + .request .create_domain_model_from_request( db, &key_store, @@ -965,7 +997,7 @@ pub async fn update_customer( ) .await?; - update_customer.generate_response(&updated_customer) + update_customer.request.generate_response(&updated_customer) } #[async_trait::async_trait] @@ -1080,9 +1112,19 @@ impl<'a> AddressStructForDbUpdate<'a> { } } +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] +#[derive(Debug)] +struct VerifyIdForUpdateCustomer<'a> { + merchant_reference_id: &'a id_type::CustomerId, + merchant_account: &'a domain::MerchantAccount, + key_store: &'a domain::MerchantKeyStore, + key_manager_state: &'a KeyManagerState, +} + +#[cfg(all(feature = "v2", feature = "customer_v2"))] +#[derive(Debug)] struct VerifyIdForUpdateCustomer<'a> { - merchant_reference_id: Option<&'a id_type::CustomerId>, - id: &'a customers::UpdateCustomerId, + id: &'a id_type::GlobalCustomerId, merchant_account: &'a domain::MerchantAccount, key_store: &'a domain::MerchantKeyStore, key_manager_state: &'a KeyManagerState, @@ -1094,18 +1136,10 @@ impl<'a> VerifyIdForUpdateCustomer<'a> { &self, db: &dyn StorageInterface, ) -> Result> { - let customer_id = self - .merchant_reference_id - .get_required_value("customer_id") - .change_context(errors::CustomersErrorResponse::InternalServerError) - .attach("Missing required field `customer_id`")?; - - let _id = self.id; - let customer = db .find_customer_by_customer_id_merchant_id( self.key_manager_state, - customer_id, + self.merchant_reference_id, self.merchant_account.get_id(), self.key_store, self.merchant_account.storage_scheme, @@ -1123,13 +1157,10 @@ impl<'a> VerifyIdForUpdateCustomer<'a> { &self, db: &dyn StorageInterface, ) -> Result> { - let id = self.id.get_global_id(); - - let _merchant_reference_id = self.merchant_reference_id; let customer = db .find_customer_by_global_id( self.key_manager_state, - &id, + self.id, self.merchant_account.get_id(), self.key_store, self.merchant_account.storage_scheme, @@ -1171,13 +1202,18 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest { let encrypted_data = types::crypto_operation( key_manager_state, type_name!(domain::Customer), - types::CryptoOperation::BatchEncrypt(CustomerRequestWithEmail::to_encryptable( - CustomerRequestWithEmail { - name: self.name.clone(), - email: self.email.clone(), - phone: self.phone.clone(), - }, - )), + types::CryptoOperation::BatchEncrypt( + domain::FromRequestEncryptableCustomer::to_encryptable( + domain::FromRequestEncryptableCustomer { + name: self.name.clone(), + email: self + .email + .as_ref() + .map(|a| a.clone().expose().switch_strategy()), + phone: self.phone.clone(), + }, + ), + ), Identifier::Merchant(key_store.merchant_id.clone()), key, ) @@ -1185,8 +1221,9 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest { .and_then(|val| val.try_into_batchoperation()) .switch()?; - let encryptable_customer = CustomerRequestWithEmail::from_encryptable(encrypted_data) - .change_context(errors::CustomersErrorResponse::InternalServerError)?; + let encryptable_customer = + domain::FromRequestEncryptableCustomer::from_encryptable(encrypted_data) + .change_context(errors::CustomersErrorResponse::InternalServerError)?; let response = db .update_customer_by_customer_id_merchant_id( @@ -1196,12 +1233,19 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest { domain_customer.to_owned(), storage::CustomerUpdate::Update { name: encryptable_customer.name, - email: encryptable_customer.email, + email: encryptable_customer.email.map(|email| { + let encryptable: Encryptable> = + Encryptable::new( + email.clone().into_inner().switch_strategy(), + email.into_encrypted(), + ); + encryptable + }), phone: Box::new(encryptable_customer.phone), phone_country_code: self.phone_country_code.clone(), metadata: self.metadata.clone(), description: self.description.clone(), - connector_customer: None, + connector_customer: Box::new(None), address_id: address.clone().map(|addr| addr.address_id), }, key_store, @@ -1239,18 +1283,20 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest { domain_customer: &'a domain::Customer, ) -> errors::CustomResult { let default_billing_address = self.get_default_customer_billing_address(); - let encrypted_customer_billing_address = default_billing_address - .async_map(|billing_address| create_encrypted_data(state, key_store, billing_address)) + .async_map(|billing_address| { + create_encrypted_data(key_manager_state, key_store, billing_address) + }) .await .transpose() .change_context(errors::CustomersErrorResponse::InternalServerError) .attach_printable("Unable to encrypt default customer billing address")?; let default_shipping_address = self.get_default_customer_shipping_address(); - let encrypted_customer_shipping_address = default_shipping_address - .async_map(|shipping_address| create_encrypted_data(state, key_store, shipping_address)) + .async_map(|shipping_address| { + create_encrypted_data(key_manager_state, key_store, shipping_address) + }) .await .transpose() .change_context(errors::CustomersErrorResponse::InternalServerError) @@ -1261,13 +1307,18 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest { let encrypted_data = types::crypto_operation( key_manager_state, type_name!(domain::Customer), - types::CryptoOperation::BatchEncrypt(CustomerRequestWithEmail::to_encryptable( - CustomerRequestWithEmail { - name: self.name.clone(), - email: self.email.clone(), - phone: self.phone.clone(), - }, - )), + types::CryptoOperation::BatchEncrypt( + domain::FromRequestEncryptableCustomer::to_encryptable( + domain::FromRequestEncryptableCustomer { + name: self.name.clone(), + email: self + .email + .as_ref() + .map(|a| a.clone().expose().switch_strategy()), + phone: self.phone.clone(), + }, + ), + ), Identifier::Merchant(key_store.merchant_id.clone()), key, ) @@ -1275,23 +1326,31 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest { .and_then(|val| val.try_into_batchoperation()) .switch()?; - let encryptable_customer = CustomerRequestWithEmail::from_encryptable(encrypted_data) - .change_context(errors::CustomersErrorResponse::InternalServerError)?; + let encryptable_customer = + domain::FromRequestEncryptableCustomer::from_encryptable(encrypted_data) + .change_context(errors::CustomersErrorResponse::InternalServerError)?; let response = db .update_customer_by_global_id( key_manager_state, - domain_customer.id.to_owned(), + &domain_customer.id, domain_customer.to_owned(), merchant_account.get_id(), storage::CustomerUpdate::Update { name: encryptable_customer.name, - email: Box::new(encryptable_customer.email), + email: Box::new(encryptable_customer.email.map(|email| { + let encryptable: Encryptable> = + Encryptable::new( + email.clone().into_inner().switch_strategy(), + email.into_encrypted(), + ); + encryptable + })), phone: Box::new(encryptable_customer.phone), phone_country_code: self.phone_country_code.clone(), metadata: self.metadata.clone(), description: self.description.clone(), - connector_customer: None, + connector_customer: Box::new(None), default_billing_address: encrypted_customer_billing_address.map(Into::into), default_shipping_address: encrypted_customer_shipping_address.map(Into::into), default_payment_method_id: Some(self.default_payment_method_id.clone()), diff --git a/crates/router/src/core/disputes.rs b/crates/router/src/core/disputes.rs index 8f083c3103a3..15bdbafb135b 100644 --- a/crates/router/src/core/disputes.rs +++ b/crates/router/src/core/disputes.rs @@ -155,7 +155,7 @@ pub async fn accept_dispute( !(dispute.dispute_stage == storage_enums::DisputeStage::Dispute && dispute.dispute_status == storage_enums::DisputeStatus::DisputeOpened), || { - metrics::ACCEPT_DISPUTE_STATUS_VALIDATION_FAILURE_METRIC.add(&metrics::CONTEXT, 1, &[]); + metrics::ACCEPT_DISPUTE_STATUS_VALIDATION_FAILURE_METRIC.add(1, &[]); Err(errors::ApiErrorResponse::DisputeStatusValidationFailed { reason: format!( "This dispute cannot be accepted because the dispute is in {} stage and has {} status", @@ -274,11 +274,7 @@ pub async fn submit_evidence( !(dispute.dispute_stage == storage_enums::DisputeStage::Dispute && dispute.dispute_status == storage_enums::DisputeStatus::DisputeOpened), || { - metrics::EVIDENCE_SUBMISSION_DISPUTE_STATUS_VALIDATION_FAILURE_METRIC.add( - &metrics::CONTEXT, - 1, - &[], - ); + metrics::EVIDENCE_SUBMISSION_DISPUTE_STATUS_VALIDATION_FAILURE_METRIC.add(1, &[]); Err(errors::ApiErrorResponse::DisputeStatusValidationFailed { reason: format!( "Evidence cannot be submitted because the dispute is in {} stage and has {} status", @@ -446,11 +442,7 @@ pub async fn attach_evidence( !(dispute.dispute_stage == storage_enums::DisputeStage::Dispute && dispute.dispute_status == storage_enums::DisputeStatus::DisputeOpened), || { - metrics::ATTACH_EVIDENCE_DISPUTE_STATUS_VALIDATION_FAILURE_METRIC.add( - &metrics::CONTEXT, - 1, - &[], - ); + metrics::ATTACH_EVIDENCE_DISPUTE_STATUS_VALIDATION_FAILURE_METRIC.add(1, &[]); Err(errors::ApiErrorResponse::DisputeStatusValidationFailed { reason: format!( "Evidence cannot be attached because the dispute is in {} stage and has {} status", diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs index c56bfaca9135..96321d097946 100644 --- a/crates/router/src/core/errors.rs +++ b/crates/router/src/core/errors.rs @@ -234,6 +234,16 @@ pub enum ApplePayDecryptionError { DerivingSharedSecretKeyFailed, } +#[derive(Debug, thiserror::Error)] +pub enum PazeDecryptionError { + #[error("Failed to base64 decode input data")] + Base64DecodingFailed, + #[error("Failed to decrypt input data")] + DecryptionFailed, + #[error("Certificate parsing failed")] + CertificateParsingFailed, +} + #[cfg(feature = "detailed_errors")] pub mod error_stack_parsing { @@ -318,6 +328,22 @@ pub enum RoutingError { VolumeSplitFailed, #[error("Unable to parse metadata")] MetadataParsingError, + #[error("Unable to retrieve success based routing config")] + SuccessBasedRoutingConfigError, + #[error("Params not found in success based routing config")] + SuccessBasedRoutingParamsNotFoundError, + #[error("Unable to calculate success based routing config from dynamic routing service")] + SuccessRateCalculationError, + #[error("Success rate client from dynamic routing gRPC service not initialized")] + SuccessRateClientInitializationError, + #[error("Unable to convert from '{from}' to '{to}'")] + GenericConversionError { from: String, to: String }, + #[error("Invalid success based connector label received from dynamic routing service: '{0}'")] + InvalidSuccessBasedConnectorLabel(String), + #[error("unable to find '{field}'")] + GenericNotFoundError { field: String }, + #[error("Unable to deserialize from '{from}' to '{to}'")] + DeserializationError { from: String, to: String }, } #[derive(Debug, Clone, thiserror::Error)] diff --git a/crates/router/src/core/errors/user.rs b/crates/router/src/core/errors/user.rs index e3c4bedab723..fa5f185ab824 100644 --- a/crates/router/src/core/errors/user.rs +++ b/crates/router/src/core/errors/user.rs @@ -90,6 +90,24 @@ pub enum UserErrors { SSOFailed, #[error("profile_id missing in JWT")] JwtProfileIdMissing, + #[error("Maximum attempts reached for TOTP")] + MaxTotpAttemptsReached, + #[error("Maximum attempts reached for Recovery Code")] + MaxRecoveryCodeAttemptsReached, + #[error("Forbidden tenant id")] + ForbiddenTenantId, + #[error("Error Uploading file to Theme Storage")] + ErrorUploadingFile, + #[error("Error Retrieving file from Theme Storage")] + ErrorRetrievingFile, + #[error("Theme not found")] + ThemeNotFound, + #[error("Theme with lineage already exists")] + ThemeAlreadyExists, + #[error("Invalid field: {0} in lineage")] + InvalidThemeLineage(String), + #[error("Missing required field: email_config")] + MissingEmailConfig, } impl common_utils::errors::ErrorSwitch for UserErrors { @@ -229,54 +247,106 @@ impl common_utils::errors::ErrorSwitch { AER::Unauthorized(ApiError::new(sub_code, 47, self.get_error_message(), None)) } + Self::MaxTotpAttemptsReached => { + AER::BadRequest(ApiError::new(sub_code, 48, self.get_error_message(), None)) + } + Self::MaxRecoveryCodeAttemptsReached => { + AER::BadRequest(ApiError::new(sub_code, 49, self.get_error_message(), None)) + } + Self::ForbiddenTenantId => { + AER::BadRequest(ApiError::new(sub_code, 50, self.get_error_message(), None)) + } + Self::ErrorUploadingFile => AER::InternalServerError(ApiError::new( + sub_code, + 51, + self.get_error_message(), + None, + )), + Self::ErrorRetrievingFile => AER::InternalServerError(ApiError::new( + sub_code, + 52, + self.get_error_message(), + None, + )), + Self::ThemeNotFound => { + AER::NotFound(ApiError::new(sub_code, 53, self.get_error_message(), None)) + } + Self::ThemeAlreadyExists => { + AER::BadRequest(ApiError::new(sub_code, 54, self.get_error_message(), None)) + } + Self::InvalidThemeLineage(_) => { + AER::BadRequest(ApiError::new(sub_code, 55, self.get_error_message(), None)) + } + Self::MissingEmailConfig => { + AER::BadRequest(ApiError::new(sub_code, 56, self.get_error_message(), None)) + } } } } impl UserErrors { - pub fn get_error_message(&self) -> &str { + pub fn get_error_message(&self) -> String { match self { - Self::InternalServerError => "Something went wrong", - Self::InvalidCredentials => "Incorrect email or password", - Self::UserNotFound => "Email doesn’t exist. Register", - Self::UserExists => "An account already exists with this email", - Self::LinkInvalid => "Invalid or expired link", - Self::UnverifiedUser => "Kindly verify your account", - Self::InvalidOldPassword => "Old password incorrect. Please enter the correct password", - Self::EmailParsingError => "Invalid Email", - Self::NameParsingError => "Invalid Name", - Self::PasswordParsingError => "Invalid Password", - Self::UserAlreadyVerified => "User already verified", - Self::CompanyNameParsingError => "Invalid Company Name", - Self::MerchantAccountCreationError(error_message) => error_message, - Self::InvalidEmailError => "Invalid Email", - Self::MerchantIdNotFound => "Invalid Merchant ID", - Self::MetadataAlreadySet => "Metadata already set", - Self::DuplicateOrganizationId => "An Organization with the id already exists", - Self::InvalidRoleId => "Invalid Role ID", - Self::InvalidRoleOperation => "User Role Operation Not Supported", - Self::IpAddressParsingFailed => "Something went wrong", - Self::InvalidMetadataRequest => "Invalid Metadata Request", - Self::MerchantIdParsingError => "Invalid Merchant Id", - Self::ChangePasswordError => "Old and new password cannot be same", - Self::InvalidDeleteOperation => "Delete Operation Not Supported", - Self::MaxInvitationsError => "Maximum invite count per request exceeded", - Self::RoleNotFound => "Role Not Found", - Self::InvalidRoleOperationWithMessage(error_message) => error_message, - Self::RoleNameParsingError => "Invalid Role Name", - 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", - Self::TotpSecretNotFound => "TOTP secret not found", - Self::UserAuthMethodAlreadyExists => "User auth method already exists", - Self::InvalidUserAuthMethodOperation => "Invalid user auth method operation", - Self::AuthConfigParsingError => "Auth config parsing error", - Self::SSOFailed => "Invalid SSO request", - Self::JwtProfileIdMissing => "profile_id missing in JWT", + Self::InternalServerError => "Something went wrong".to_string(), + Self::InvalidCredentials => "Incorrect email or password".to_string(), + Self::UserNotFound => "Email doesn’t exist. Register".to_string(), + Self::UserExists => "An account already exists with this email".to_string(), + Self::LinkInvalid => "Invalid or expired link".to_string(), + Self::UnverifiedUser => "Kindly verify your account".to_string(), + Self::InvalidOldPassword => { + "Old password incorrect. Please enter the correct password".to_string() + } + Self::EmailParsingError => "Invalid Email".to_string(), + Self::NameParsingError => "Invalid Name".to_string(), + Self::PasswordParsingError => "Invalid Password".to_string(), + Self::UserAlreadyVerified => "User already verified".to_string(), + Self::CompanyNameParsingError => "Invalid Company Name".to_string(), + Self::MerchantAccountCreationError(error_message) => error_message.to_string(), + Self::InvalidEmailError => "Invalid Email".to_string(), + Self::MerchantIdNotFound => "Invalid Merchant ID".to_string(), + Self::MetadataAlreadySet => "Metadata already set".to_string(), + Self::DuplicateOrganizationId => { + "An Organization with the id already exists".to_string() + } + Self::InvalidRoleId => "Invalid Role ID".to_string(), + Self::InvalidRoleOperation => "User Role Operation Not Supported".to_string(), + Self::IpAddressParsingFailed => "Something went wrong".to_string(), + Self::InvalidMetadataRequest => "Invalid Metadata Request".to_string(), + Self::MerchantIdParsingError => "Invalid Merchant Id".to_string(), + Self::ChangePasswordError => "Old and new password cannot be same".to_string(), + Self::InvalidDeleteOperation => "Delete Operation Not Supported".to_string(), + Self::MaxInvitationsError => "Maximum invite count per request exceeded".to_string(), + Self::RoleNotFound => "Role Not Found".to_string(), + Self::InvalidRoleOperationWithMessage(error_message) => error_message.to_string(), + Self::RoleNameParsingError => "Invalid Role Name".to_string(), + Self::RoleNameAlreadyExists => "Role name already exists".to_string(), + Self::TotpNotSetup => "TOTP not setup".to_string(), + Self::InvalidTotp => "Invalid TOTP".to_string(), + Self::TotpRequired => "TOTP required".to_string(), + Self::InvalidRecoveryCode => "Invalid Recovery Code".to_string(), + Self::MaxTotpAttemptsReached => "Maximum attempts reached for TOTP".to_string(), + Self::MaxRecoveryCodeAttemptsReached => { + "Maximum attempts reached for Recovery Code".to_string() + } + Self::TwoFactorAuthRequired => "Two factor auth required".to_string(), + Self::TwoFactorAuthNotSetup => "Two factor auth not setup".to_string(), + Self::TotpSecretNotFound => "TOTP secret not found".to_string(), + Self::UserAuthMethodAlreadyExists => "User auth method already exists".to_string(), + Self::InvalidUserAuthMethodOperation => { + "Invalid user auth method operation".to_string() + } + Self::AuthConfigParsingError => "Auth config parsing error".to_string(), + Self::SSOFailed => "Invalid SSO request".to_string(), + Self::JwtProfileIdMissing => "profile_id missing in JWT".to_string(), + Self::ForbiddenTenantId => "Forbidden tenant id".to_string(), + Self::ErrorUploadingFile => "Error Uploading file to Theme Storage".to_string(), + Self::ErrorRetrievingFile => "Error Retrieving file from Theme Storage".to_string(), + Self::ThemeNotFound => "Theme not found".to_string(), + Self::ThemeAlreadyExists => "Theme with lineage already exists".to_string(), + Self::InvalidThemeLineage(field_name) => { + format!("Invalid field: {} in lineage", field_name) + } + Self::MissingEmailConfig => "Missing required field: email_config".to_string(), } } } diff --git a/crates/router/src/core/fraud_check.rs b/crates/router/src/core/fraud_check.rs index 70c35b460e86..99a60817303a 100644 --- a/crates/router/src/core/fraud_check.rs +++ b/crates/router/src/core/fraud_check.rs @@ -588,6 +588,7 @@ pub async fn post_payment_frm_core<'a, F, D>( customer: &Option, key_store: domain::MerchantKeyStore, should_continue_capture: &mut bool, + platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult> where F: Send + Clone, @@ -647,6 +648,7 @@ where payment_data, customer, should_continue_capture, + platform_merchant_account, ) .await?; logger::debug!("frm_post_tasks_data: {:?}", frm_data); 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 92dc2a99e8a6..c413208b2083 100644 --- a/crates/router/src/core/fraud_check/flows/checkout_flow.rs +++ b/crates/router/src/core/fraud_check/flows/checkout_flow.rs @@ -5,7 +5,6 @@ use masking::ExposeInterface; use super::{ConstructFlowSpecificData, FeatureFrm}; use crate::{ - connector::utils::PaymentsAttemptData, core::{ errors::{ConnectorErrorExt, RouterResult}, fraud_check::types::FrmData, @@ -37,9 +36,9 @@ impl ConstructFlowSpecificData, - _merchant_connector_account: &helpers::MerchantConnectorAccountType, + _merchant_connector_account: &domain::MerchantConnectorAccount, _merchant_recipient_data: Option, - _header_payload: Option, + _header_payload: Option, ) -> RouterResult> { todo!() @@ -55,9 +54,11 @@ impl ConstructFlowSpecificData, merchant_connector_account: &helpers::MerchantConnectorAccountType, _merchant_recipient_data: Option, - header_payload: Option, + header_payload: Option, ) -> RouterResult> { + use crate::connector::utils::PaymentsAttemptData; + let status = storage_enums::AttemptStatus::Pending; let auth_type: ConnectorAuthType = merchant_connector_account @@ -84,7 +85,6 @@ impl ConstructFlowSpecificData( payment_method, connector_auth_type: auth_type, description: None, - return_url: payment_intent.return_url.clone(), address: PaymentAddress::default(), auth_type: payment_attempt.authentication_type.unwrap_or_default(), connector_meta_data: merchant_connector_account.get_metadata(), @@ -89,7 +88,10 @@ pub async fn construct_fulfillment_router_data<'a>( minor_amount_captured: payment_intent.amount_captured, payment_method_status: None, request: FraudCheckFulfillmentData { - amount: payment_attempt.amount.get_amount_as_i64(), + amount: payment_attempt + .net_amount + .get_total_amount() + .get_amount_as_i64(), order_details: payment_intent.order_details.clone(), fulfillment_req: fulfillment_request, }, @@ -124,6 +126,9 @@ pub async fn construct_fulfillment_router_data<'a>( integrity_check: Ok(()), additional_merchant_data: None, header_payload: None, + connector_mandate_request_reference_id: None, + authentication_id: None, + psd2_sca_exemption_type: None, }; Ok(router_data) } 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 e4d002931ec2..5e3540311854 100644 --- a/crates/router/src/core/fraud_check/flows/record_return.rs +++ b/crates/router/src/core/fraud_check/flows/record_return.rs @@ -34,9 +34,9 @@ impl ConstructFlowSpecificData, - _merchant_connector_account: &helpers::MerchantConnectorAccountType, + _merchant_connector_account: &domain::MerchantConnectorAccount, _merchant_recipient_data: Option, - _header_payload: Option, + _header_payload: Option, ) -> RouterResult> { todo!() @@ -52,7 +52,7 @@ impl ConstructFlowSpecificData, merchant_connector_account: &helpers::MerchantConnectorAccountType, _merchant_recipient_data: Option, - header_payload: Option, + header_payload: Option, ) -> RouterResult> { let status = storage_enums::AttemptStatus::Pending; @@ -80,7 +80,6 @@ impl ConstructFlowSpecificData, - _merchant_connector_account: &helpers::MerchantConnectorAccountType, + _merchant_connector_account: &domain::MerchantConnectorAccount, _merchant_recipient_data: Option, - _header_payload: Option, + _header_payload: Option, ) -> RouterResult> { todo!() } @@ -49,7 +49,7 @@ impl ConstructFlowSpecificData, merchant_connector_account: &helpers::MerchantConnectorAccountType, _merchant_recipient_data: Option, - header_payload: Option, + header_payload: Option, ) -> RouterResult> { let status = storage_enums::AttemptStatus::Pending; @@ -76,7 +76,6 @@ impl ConstructFlowSpecificData, - _merchant_connector_account: &helpers::MerchantConnectorAccountType, + _merchant_connector_account: &domain::MerchantConnectorAccount, _merchant_recipient_data: Option, - _header_payload: Option, + _header_payload: Option, ) -> RouterResult< RouterData, > { @@ -56,7 +56,7 @@ impl customer: &Option, merchant_connector_account: &helpers::MerchantConnectorAccountType, _merchant_recipient_data: Option, - header_payload: Option, + header_payload: Option, ) -> RouterResult< RouterData, > { @@ -88,7 +88,6 @@ impl .ok_or(errors::ApiErrorResponse::PaymentMethodNotFound)?, connector_auth_type: auth_type, description: None, - return_url: None, address: self.address.clone(), auth_type: storage_enums::AuthenticationType::NoThreeDs, connector_meta_data: None, @@ -96,13 +95,20 @@ impl amount_captured: None, minor_amount_captured: None, request: FraudCheckTransactionData { - amount: self.payment_attempt.amount.get_amount_as_i64(), + amount: self + .payment_attempt + .net_amount + .get_total_amount() + .get_amount_as_i64(), order_details: self.order_details.clone(), currency, payment_method, error_code: self.payment_attempt.error_code.clone(), error_message: self.payment_attempt.error_message.clone(), - connector_transaction_id: self.payment_attempt.connector_transaction_id.clone(), + connector_transaction_id: self + .payment_attempt + .get_connector_payment_id() + .map(ToString::to_string), connector: self.payment_attempt.connector.clone(), }, // self.order_details response: Ok(FraudCheckResponseData::TransactionResponse { @@ -138,6 +144,9 @@ impl integrity_check: Ok(()), additional_merchant_data: None, header_payload, + connector_mandate_request_reference_id: None, + authentication_id: None, + psd2_sca_exemption_type: None, }; Ok(router_data) diff --git a/crates/router/src/core/fraud_check/operation.rs b/crates/router/src/core/fraud_check/operation.rs index d802339b675a..721afaa1e499 100644 --- a/crates/router/src/core/fraud_check/operation.rs +++ b/crates/router/src/core/fraud_check/operation.rs @@ -85,6 +85,7 @@ pub trait Domain: Send + Sync { _payment_data: &mut D, _customer: &Option, _should_continue_capture: &mut bool, + _platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult> where F: Send + Clone, diff --git a/crates/router/src/core/fraud_check/operation/fraud_check_post.rs b/crates/router/src/core/fraud_check/operation/fraud_check_post.rs index 8a9a91fb1e7a..69f06e594166 100644 --- a/crates/router/src/core/fraud_check/operation/fraud_check_post.rs +++ b/crates/router/src/core/fraud_check/operation/fraud_check_post.rs @@ -1,9 +1,8 @@ -use api_models::payments::HeaderPayload; use async_trait::async_trait; use common_enums::{CaptureMethod, FrmSuggestion}; use common_utils::ext_traits::Encode; use hyperswitch_domain_models::payments::{ - payment_attempt::PaymentAttemptUpdate, payment_intent::PaymentIntentUpdate, + payment_attempt::PaymentAttemptUpdate, payment_intent::PaymentIntentUpdate, HeaderPayload, }; use router_env::{instrument, logger, tracing}; @@ -227,6 +226,7 @@ where _payment_data: &mut D, _customer: &Option, _should_continue_capture: &mut bool, + _platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult> { todo!() } @@ -245,6 +245,7 @@ where payment_data: &mut D, customer: &Option, _should_continue_capture: &mut bool, + platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult> { if matches!(frm_data.fraud_check.frm_status, FraudCheckStatus::Fraud) && matches!( @@ -278,6 +279,7 @@ where payments::CallConnectorAction::Trigger, None, HeaderPayload::default(), + platform_merchant_account.cloned(), )) .await?; logger::debug!("payment_id : {:?} has been cancelled since it has been found fraudulent by configured frm connector",payment_data.get_payment_attempt().payment_id); @@ -304,7 +306,7 @@ where } else if matches!(frm_data.fraud_check.frm_status, FraudCheckStatus::Legit) && matches!( frm_data.fraud_check.payment_capture_method, - Some(CaptureMethod::Automatic) + Some(CaptureMethod::Automatic) | Some(CaptureMethod::SequentialAutomatic) ) { let capture_request = api_models::payments::PaymentsCaptureRequest { @@ -335,6 +337,7 @@ where payments::CallConnectorAction::Trigger, None, HeaderPayload::default(), + platform_merchant_account.cloned(), )) .await?; logger::debug!("payment_id : {:?} has been captured since it has been found legit by configured frm connector",payment_data.get_payment_attempt().payment_id); diff --git a/crates/router/src/core/fraud_check/types.rs b/crates/router/src/core/fraud_check/types.rs index 3f5988777fd5..2aa486fdb3c7 100644 --- a/crates/router/src/core/fraud_check/types.rs +++ b/crates/router/src/core/fraud_check/types.rs @@ -7,8 +7,11 @@ use api_models::{ use common_enums::FrmSuggestion; 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, +pub use hyperswitch_domain_models::{ + router_request_types::fraud_check::{ + Address, Destination, FrmFulfillmentRequest, FulfillmentStatus, Fulfillments, Product, + }, + types::OrderDetailsWithAmount, }; use masking::Serialize; use serde::Deserialize; @@ -54,7 +57,7 @@ pub struct FrmData { pub fraud_check: FraudCheck, pub address: PaymentAddress, pub connector_details: ConnectorDetailsCore, - pub order_details: Option>, + pub order_details: Option>, pub refund: Option, pub frm_metadata: Option, } @@ -79,7 +82,7 @@ pub struct PaymentToFrmData { pub merchant_account: MerchantAccount, pub address: PaymentAddress, pub connector_details: ConnectorDetailsCore, - pub order_details: Option>, + pub order_details: Option>, pub frm_metadata: Option, } diff --git a/crates/router/src/core/generic_link/payout_link/initiate/script.js b/crates/router/src/core/generic_link/payout_link/initiate/script.js index 2d3592256e25..f226a06fa16a 100644 --- a/crates/router/src/core/generic_link/payout_link/initiate/script.js +++ b/crates/router/src/core/generic_link/payout_link/initiate/script.js @@ -144,6 +144,7 @@ if (!isTestMode && !isFramed) { // @ts-ignore hyper = window.Hyper(publishableKey, { isPreloadEnabled: false, + shouldUseTopRedirection: isFramed, }); widgets = hyper.widgets({ appearance: appearance, diff --git a/crates/router/src/core/gsm.rs b/crates/router/src/core/gsm.rs index 4a678a5c4089..c7daee111a9a 100644 --- a/crates/router/src/core/gsm.rs +++ b/crates/router/src/core/gsm.rs @@ -67,6 +67,7 @@ pub async fn update_gsm_rule( step_up_possible, unified_code, unified_message, + error_category, } = gsm_request; GsmInterface::update_gsm_rule( db, @@ -82,6 +83,7 @@ pub async fn update_gsm_rule( step_up_possible, unified_code, unified_message, + error_category, }, ) .await diff --git a/crates/router/src/core/health_check.rs b/crates/router/src/core/health_check.rs index 83faee677d43..31e8cc75f5bd 100644 --- a/crates/router/src/core/health_check.rs +++ b/crates/router/src/core/health_check.rs @@ -1,5 +1,7 @@ #[cfg(feature = "olap")] use analytics::health_check::HealthCheck; +#[cfg(feature = "dynamic_routing")] +use api_models::health_check::HealthCheckMap; use api_models::health_check::HealthState; use error_stack::ResultExt; use router_env::logger; @@ -28,6 +30,11 @@ pub trait HealthCheckInterface { async fn health_check_opensearch( &self, ) -> CustomResult; + + #[cfg(feature = "dynamic_routing")] + async fn health_check_grpc( + &self, + ) -> CustomResult; } #[async_trait::async_trait] @@ -158,4 +165,20 @@ impl HealthCheckInterface for app::SessionState { logger::debug!("Outgoing request successful"); Ok(HealthState::Running) } + + #[cfg(feature = "dynamic_routing")] + async fn health_check_grpc( + &self, + ) -> CustomResult { + let health_client = &self.grpc_client.health_client; + let grpc_config = &self.conf.grpc_client; + + let health_check_map = health_client + .perform_health_check(grpc_config) + .await + .change_context(errors::HealthCheckGRPCServiceError::FailedToCallService)?; + + logger::debug!("Health check successful"); + Ok(health_check_map) + } } diff --git a/crates/router/src/core/locker_migration.rs b/crates/router/src/core/locker_migration.rs index 8fd4b10ed3fc..dbadadb3a6fc 100644 --- a/crates/router/src/core/locker_migration.rs +++ b/crates/router/src/core/locker_migration.rs @@ -138,10 +138,12 @@ pub async fn call_to_locker( ) -> CustomResult { let mut cards_moved = 0; - for pm in payment_methods - .into_iter() - .filter(|pm| matches!(pm.payment_method, Some(storage_enums::PaymentMethod::Card))) - { + for pm in payment_methods.into_iter().filter(|pm| { + matches!( + pm.get_payment_method_type(), + Some(storage_enums::PaymentMethod::Card) + ) + }) { let card = cards::get_card_from_locker( state, customer_id, @@ -171,8 +173,8 @@ pub async fn call_to_locker( }; let pm_create = api::PaymentMethodCreate { - payment_method: pm.payment_method, - payment_method_type: pm.payment_method_type, + payment_method: pm.get_payment_method_type(), + payment_method_type: pm.get_payment_method_subtype(), payment_method_issuer: pm.payment_method_issuer, payment_method_issuer_code: pm.payment_method_issuer_code, card: Some(card_details.clone()), diff --git a/crates/router/src/core/mandate.rs b/crates/router/src/core/mandate.rs index b5707979247b..15ceb9b1da21 100644 --- a/crates/router/src/core/mandate.rs +++ b/crates/router/src/core/mandate.rs @@ -5,7 +5,7 @@ use common_utils::{ext_traits::Encode, id_type}; use diesel_models::enums as storage_enums; use error_stack::{report, ResultExt}; use futures::future; -use router_env::{instrument, logger, metrics::add_attributes, tracing}; +use router_env::{instrument, logger, tracing}; use super::payments::helpers as payment_helper; use crate::{ @@ -19,7 +19,6 @@ use crate::{ types::{ self, api::{ - customers, mandates::{self, MandateResponseExt}, ConnectorData, GetToken, }, @@ -224,27 +223,24 @@ pub async fn update_connector_mandate_id( } Ok(services::ApplicationResponse::StatusOk) } - +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] #[instrument(skip(state))] pub async fn get_customer_mandates( state: SessionState, merchant_account: domain::MerchantAccount, key_store: domain::MerchantKeyStore, - req: customers::CustomerId, + customer_id: id_type::CustomerId, ) -> RouterResponse> { let mandates = state .store - .find_mandate_by_merchant_id_customer_id( - merchant_account.get_id(), - &req.get_merchant_reference_id(), - ) + .find_mandate_by_merchant_id_customer_id(merchant_account.get_id(), &customer_id) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable_lazy(|| { format!( "Failed while finding mandate: merchant_id: {:?}, customer_id: {:?}", merchant_account.get_id(), - req.get_merchant_reference_id() + customer_id, ) })?; @@ -341,9 +337,8 @@ where .change_context(errors::ApiErrorResponse::MandateUpdateFailed), }?; metrics::SUBSEQUENT_MANDATE_PAYMENT.add( - &metrics::CONTEXT, 1, - &add_attributes([("connector", mandate.connector)]), + router_env::metric_attributes!(("connector", mandate.connector)), ); Ok(Some(mandate_id.clone())) } @@ -357,10 +352,10 @@ where network_txn_id, .. } => (mandate_reference.clone(), network_txn_id.clone()), - _ => (None, None), + _ => (Box::new(None), None), }; - let mandate_ids = mandate_reference + let mandate_ids = (*mandate_reference) .as_ref() .map(|md| { md.encode_to_value() @@ -379,7 +374,7 @@ where mandate_ids, network_txn_id, get_insensitive_payment_method_data_if_exists(resp), - mandate_reference, + *mandate_reference, merchant_connector_id, )? else { @@ -396,11 +391,7 @@ where .insert_mandate(new_mandate_data, storage_scheme) .await .to_duplicate_response(errors::ApiErrorResponse::DuplicateMandate)?; - metrics::MANDATE_COUNT.add( - &metrics::CONTEXT, - 1, - &add_attributes([("connector", connector)]), - ); + metrics::MANDATE_COUNT.add(1, router_env::metric_attributes!(("connector", connector))); Ok(Some(res_mandate_id)) } } @@ -439,7 +430,7 @@ impl ForeignFrom> match resp { Ok(types::PaymentsResponseData::TransactionResponse { mandate_reference, .. - }) => mandate_reference, + }) => *mandate_reference, _ => None, } } diff --git a/crates/router/src/core/mandate/utils.rs b/crates/router/src/core/mandate/utils.rs index 544f9ea756ec..5418d7b7a70b 100644 --- a/crates/router/src/core/mandate/utils.rs +++ b/crates/router/src/core/mandate/utils.rs @@ -42,7 +42,6 @@ pub async fn construct_mandate_revoke_router_data( payment_method: diesel_models::enums::PaymentMethod::default(), connector_auth_type: auth_type, description: None, - return_url: None, address: PaymentAddress::default(), auth_type: diesel_models::enums::AuthenticationType::default(), connector_meta_data: None, @@ -80,6 +79,9 @@ pub async fn construct_mandate_revoke_router_data( integrity_check: Ok(()), additional_merchant_data: None, header_payload: None, + connector_mandate_request_reference_id: None, + authentication_id: None, + psd2_sca_exemption_type: None, }; Ok(router_data) diff --git a/crates/router/src/core/metrics.rs b/crates/router/src/core/metrics.rs index 8956c38e7edd..a98a4ffb259f 100644 --- a/crates/router/src/core/metrics.rs +++ b/crates/router/src/core/metrics.rs @@ -1,7 +1,5 @@ -pub use router_env::opentelemetry::KeyValue; -use router_env::{counter_metric, global_meter, metrics_context}; +use router_env::{counter_metric, global_meter}; -metrics_context!(CONTEXT); global_meter!(GLOBAL_METER, "ROUTER_API"); counter_metric!(INCOMING_DISPUTE_WEBHOOK_METRIC, GLOBAL_METER); // No. of incoming dispute webhooks diff --git a/crates/router/src/core/payment_link.rs b/crates/router/src/core/payment_link.rs index 100e7abc85eb..498a00c240df 100644 --- a/crates/router/src/core/payment_link.rs +++ b/crates/router/src/core/payment_link.rs @@ -5,13 +5,9 @@ use api_models::{ payments::{PaymentLinkData, PaymentLinkStatusWrap}, }; use common_utils::{ - consts::{ - DEFAULT_ALLOWED_DOMAINS, DEFAULT_BACKGROUND_COLOR, DEFAULT_DISPLAY_SDK_ONLY, - DEFAULT_ENABLE_SAVED_PAYMENT_METHOD, DEFAULT_LOCALE, DEFAULT_MERCHANT_LOGO, - DEFAULT_PRODUCT_IMG, DEFAULT_SDK_LAYOUT, DEFAULT_SESSION_EXPIRY, - }, + consts::{DEFAULT_LOCALE, DEFAULT_SESSION_EXPIRY}, ext_traits::{AsyncExt, OptionExt, ValueExt}, - types::{AmountConvertor, MinorUnit, StringMajorUnitForCore}, + types::{AmountConvertor, StringMajorUnitForCore}, }; use error_stack::{report, ResultExt}; use futures::future; @@ -25,7 +21,11 @@ use super::{ payments::helpers, }; use crate::{ - consts, + consts::{ + self, DEFAULT_ALLOWED_DOMAINS, DEFAULT_BACKGROUND_COLOR, DEFAULT_DISPLAY_SDK_ONLY, + DEFAULT_ENABLE_SAVED_PAYMENT_METHOD, DEFAULT_HIDE_CARD_NICKNAME_FIELD, + DEFAULT_MERCHANT_LOGO, DEFAULT_PRODUCT_IMG, DEFAULT_SDK_LAYOUT, DEFAULT_SHOW_CARD_FORM, + }, errors::RouterResponse, get_payment_link_config_value, get_payment_link_config_value_based_on_priority, headers::ACCEPT_LANGUAGE, @@ -35,7 +35,7 @@ use crate::{ api::payment_link::PaymentLinkResponseExt, domain, storage::{enums as storage_enums, payment_link::PaymentLink}, - transformers::ForeignFrom, + transformers::{ForeignFrom, ForeignInto}, }, }; @@ -125,8 +125,14 @@ pub async fn form_payment_link_data( sdk_layout: DEFAULT_SDK_LAYOUT.to_owned(), display_sdk_only: DEFAULT_DISPLAY_SDK_ONLY, enabled_saved_payment_method: DEFAULT_ENABLE_SAVED_PAYMENT_METHOD, + hide_card_nickname_field: DEFAULT_HIDE_CARD_NICKNAME_FIELD, + show_card_form_by_default: DEFAULT_SHOW_CARD_FORM, allowed_domains: DEFAULT_ALLOWED_DOMAINS, transaction_details: None, + background_image: None, + details_layout: None, + branding_visibility: None, + payment_button_text: None, } }; @@ -180,7 +186,7 @@ pub async fn form_payment_link_data( let payment_link_status = check_payment_link_status(session_expiry); let is_terminal_state = check_payment_link_invalid_conditions( - &payment_intent.status, + payment_intent.status, &[ storage_enums::IntentStatus::Cancelled, storage_enums::IntentStatus::Failed, @@ -265,8 +271,14 @@ pub async fn form_payment_link_data( merchant_description: payment_intent.description, sdk_layout: payment_link_config.sdk_layout.clone(), display_sdk_only: payment_link_config.display_sdk_only, + hide_card_nickname_field: payment_link_config.hide_card_nickname_field, + show_card_form_by_default: payment_link_config.show_card_form_by_default, locale, transaction_details: payment_link_config.transaction_details.clone(), + background_image: payment_link_config.background_image.clone(), + details_layout: payment_link_config.details_layout, + branding_visibility: payment_link_config.branding_visibility, + payment_button_text: payment_link_config.payment_button_text.clone(), }; Ok(( @@ -322,7 +334,10 @@ pub async fn initiate_secure_payment_link_flow( PaymentLinkData::PaymentLinkDetails(link_details) => { let secure_payment_link_details = api_models::payments::SecurePaymentLinkDetails { enabled_saved_payment_method: payment_link_config.enabled_saved_payment_method, + hide_card_nickname_field: payment_link_config.hide_card_nickname_field, + show_card_form_by_default: payment_link_config.show_card_form_by_default, payment_link_details: *link_details.to_owned(), + payment_button_text: payment_link_config.payment_button_text, }; let js_script = format!( "window.__PAYMENT_DETAILS = {}", @@ -547,7 +562,7 @@ fn validate_order_details( .clone_from(&order.product_img_link) }; order_details_amount_string.amount = required_conversion_type - .convert(MinorUnit::new(order.amount), currency) + .convert(order.amount, currency) .change_context(errors::ApiErrorResponse::AmountConversionFailed { amount_type: "StringMajorUnit", })?; @@ -580,18 +595,16 @@ pub fn get_payment_link_config_based_on_priority( default_domain_name: String, payment_link_config_id: Option, ) -> Result<(PaymentLinkConfig, String), error_stack::Report> { - let (domain_name, business_theme_configs, allowed_domains) = + let (domain_name, business_theme_configs, allowed_domains, branding_visibility) = if let Some(business_config) = business_link_config { - logger::info!( - "domain name set to custom domain https://{:?}", - business_config.domain_name - ); - ( business_config .domain_name .clone() - .map(|d_name| format!("https://{}", d_name)) + .map(|d_name| { + logger::info!("domain name set to custom domain https://{:?}", d_name); + format!("https://{}", d_name) + }) .unwrap_or_else(|| default_domain_name.clone()), payment_link_config_id .and_then(|id| { @@ -602,12 +615,22 @@ pub fn get_payment_link_config_based_on_priority( }) .or(business_config.default_config), business_config.allowed_domains, + business_config.branding_visibility, ) } else { - (default_domain_name, None, None) + (default_domain_name, None, None, None) }; - let (theme, logo, seller_name, sdk_layout, display_sdk_only, enabled_saved_payment_method) = get_payment_link_config_value!( + let ( + theme, + logo, + seller_name, + sdk_layout, + display_sdk_only, + enabled_saved_payment_method, + hide_card_nickname_field, + show_card_form_by_default, + ) = get_payment_link_config_value!( payment_create_link_config, business_theme_configs, (theme, DEFAULT_BACKGROUND_COLOR.to_string()), @@ -618,19 +641,61 @@ pub fn get_payment_link_config_based_on_priority( ( enabled_saved_payment_method, DEFAULT_ENABLE_SAVED_PAYMENT_METHOD - ) + ), + (hide_card_nickname_field, DEFAULT_HIDE_CARD_NICKNAME_FIELD), + (show_card_form_by_default, DEFAULT_SHOW_CARD_FORM) ); - let payment_link_config = PaymentLinkConfig { - theme, - logo, - seller_name, - sdk_layout, - display_sdk_only, - enabled_saved_payment_method, - allowed_domains, - transaction_details: payment_create_link_config - .and_then(|payment_link_config| payment_link_config.theme_config.transaction_details), - }; + let payment_link_config = + PaymentLinkConfig { + theme, + logo, + seller_name, + sdk_layout, + display_sdk_only, + enabled_saved_payment_method, + hide_card_nickname_field, + show_card_form_by_default, + allowed_domains, + branding_visibility, + transaction_details: payment_create_link_config.as_ref().and_then( + |payment_link_config| payment_link_config.theme_config.transaction_details.clone(), + ), + details_layout: payment_create_link_config + .as_ref() + .and_then(|payment_link_config| payment_link_config.theme_config.details_layout) + .or_else(|| { + business_theme_configs + .as_ref() + .and_then(|business_theme_config| business_theme_config.details_layout) + }), + background_image: payment_create_link_config + .as_ref() + .and_then(|payment_link_config| { + payment_link_config.theme_config.background_image.clone() + }) + .or_else(|| { + business_theme_configs + .as_ref() + .and_then(|business_theme_config| { + business_theme_config + .background_image + .as_ref() + .map(|background_image| background_image.clone().foreign_into()) + }) + }), + payment_button_text: payment_create_link_config + .as_ref() + .and_then(|payment_link_config| { + payment_link_config.theme_config.payment_button_text.clone() + }) + .or_else(|| { + business_theme_configs + .as_ref() + .and_then(|business_theme_config| { + business_theme_config.payment_button_text.clone() + }) + }), + }; Ok((payment_link_config, domain_name)) } @@ -649,10 +714,10 @@ fn capitalize_first_char(s: &str) -> String { } fn check_payment_link_invalid_conditions( - intent_status: &storage_enums::IntentStatus, + intent_status: storage_enums::IntentStatus, not_allowed_statuses: &[storage_enums::IntentStatus], ) -> bool { - not_allowed_statuses.contains(intent_status) + not_allowed_statuses.contains(&intent_status) } #[cfg(feature = "v2")] @@ -729,8 +794,14 @@ pub async fn get_payment_link_status( sdk_layout: DEFAULT_SDK_LAYOUT.to_owned(), display_sdk_only: DEFAULT_DISPLAY_SDK_ONLY, enabled_saved_payment_method: DEFAULT_ENABLE_SAVED_PAYMENT_METHOD, + hide_card_nickname_field: DEFAULT_HIDE_CARD_NICKNAME_FIELD, + show_card_form_by_default: DEFAULT_SHOW_CARD_FORM, allowed_domains: DEFAULT_ALLOWED_DOMAINS, transaction_details: None, + background_image: None, + details_layout: None, + branding_visibility: None, + payment_button_text: None, } }; @@ -744,7 +815,7 @@ pub async fn get_payment_link_status( let required_conversion_type = StringMajorUnitForCore; let amount = required_conversion_type - .convert(payment_attempt.net_amount, currency) + .convert(payment_attempt.get_total_amount(), currency) .change_context(errors::ApiErrorResponse::AmountConversionFailed { amount_type: "StringMajorUnit", })?; diff --git a/crates/router/src/core/payment_link/locale.js b/crates/router/src/core/payment_link/locale.js index ce727c40c761..114376bf9f9f 100644 --- a/crates/router/src/core/payment_link/locale.js +++ b/crates/router/src/core/payment_link/locale.js @@ -3,11 +3,11 @@ The languages supported by locale.js are: 1) English (en) 2) Hebrew (he) 3) French (fr) - 4) British English (en_GB) + 4) British English (en_gb) 5) Arabic (ar) 6) Japanese (ja) 7) German (de) - 8) Belgian French (fr_BE) + 8) Belgian French (fr_be) 9) Spanish (es) 10) Catalan (ca) 11) Portuguese (pt) @@ -17,6 +17,7 @@ The languages supported by locale.js are: 15) Swedish (sv) 16) Russian (ru) 17) Chinese (zh) + 19) Traditional Chinese (zh_hant) */ const locales = { en: { @@ -112,7 +113,7 @@ const locales = { errorCode: "Code d'erreur", errorMessage: "Message d'erreur" }, - en_GB: { + en_gb: { expiresOn: "Link expires on: ", refId: "Ref Id: ", requestedBy: "Requested by ", @@ -237,7 +238,7 @@ const locales = { errorCode: "Fehlercode", errorMessage: "Fehlermeldung" }, - fr_BE: { + fr_be: { expiresOn: "Le lien expire le: ", refId: "ID de référence: ", requestedBy: "Demandé par ", @@ -548,10 +549,42 @@ const locales = { notAllowed: "您没有权限查看此内容。", errorCode: "错误代码", errorMessage: "错误信息" - } + }, + zh_hant: { + expiresOn: "連結到期日期:", + refId: "參考編號:", + requestedBy: "請求者 ", + payNow: "立即付款", + yourCart: "你的購物車", + quantity: "數量", + showLess: "顯示較少", + showMore: "顯示更多", + miscellaneousCharges: "雜項費用", + miscellaneousChargesDetail: "(包括稅金、運費、折扣、優惠等)", + paymentTakingLonger: "抱歉!您的付款處理時間比預期長。請稍後再查看。", + paymentLinkExpired: "付款連結已過期", + paymentReceived: "我們已成功收到您的付款", + paymentLinkExpiredMessage: "抱歉,此付款連結已過期。請使用以下參考進行進一步調查。", + paidSuccessfully: "付款成功", + paymentPending: "付款待處理", + paymentFailed: "付款失敗!", + paymentCancelled: "付款已取消", + paymentUnderReview: "付款正在審核中", + paymentSuccess: "支付成功", + partialPaymentCaptured: "部分付款已被捕獲。", + somethingWentWrong: "出了點問題", + redirecting: "重定向...", + redirectingIn: "重定向到", + seconds: " 秒...", + unexpectedError: "發生了意外錯誤。", + notAllowed: "您無權查看此內容。", + errorCode: "錯誤代碼", + errorMessage: "錯誤訊息" + }, }; function getTranslations(locale_str) { - var locale = locale_str || 'en'; // defaults if locale is not present in payment details. + var fallback_locale = 'en'; + var locale = locale_str.toLowerCase().replace(/-/g, "_") || fallback_locale; // defaults if locale is not present in payment details. return locales[locale] || locales['en']; // defaults if locale is not implemented in locales. } \ No newline at end of file diff --git a/crates/router/src/core/payment_link/payment_link_initiate/payment_link.css b/crates/router/src/core/payment_link/payment_link_initiate/payment_link.css index 65a50e967f3b..b8ccdeba33aa 100644 --- a/crates/router/src/core/payment_link/payment_link_initiate/payment_link.css +++ b/crates/router/src/core/payment_link/payment_link_initiate/payment_link.css @@ -71,6 +71,7 @@ body { #hyper-checkout-details { font-family: "Montserrat"; + background-repeat: no-repeat; } .hyper-checkout-payment { @@ -144,7 +145,7 @@ body { #hyper-checkout-merchant-image, #hyper-checkout-cart-image { height: 64px; - width: 64px; + padding: 0 10px; border-radius: 4px; display: flex; align-self: flex-start; @@ -153,8 +154,7 @@ body { } #hyper-checkout-merchant-image > img { - height: 48px; - width: 48px; + height: 40px; } #hyper-checkout-cart-image { @@ -279,7 +279,7 @@ body { #hyper-checkout-merchant-description { font-size: 13px; - color: #808080; + margin: 10px 0 20px 0; } .powered-by-hyper { @@ -292,7 +292,6 @@ body { min-width: 584px; z-index: 2; background-color: var(--primary-color); - box-shadow: 0px 1px 10px #f2f2f2; display: flex; flex-flow: column; align-items: center; @@ -665,6 +664,12 @@ body { animation: loading 1s linear infinite; } +@media only screen and (min-width: 1199px) { + #hyper-checkout-merchant-description { + color: #808080; + } +} + @media only screen and (max-width: 1199px) { body { overflow-y: scroll; diff --git a/crates/router/src/core/payment_link/payment_link_initiate/payment_link.html b/crates/router/src/core/payment_link/payment_link_initiate/payment_link.html index 963b4d6083a6..9abcc440068a 100644 --- a/crates/router/src/core/payment_link/payment_link_initiate/payment_link.html +++ b/crates/router/src/core/payment_link/payment_link_initiate/payment_link.html @@ -185,7 +185,7 @@
-
+
@@ -313,17 +301,6 @@
- "#)) + (PreEscaped(r#" + + "#)) + h3 style="text-align: center;" { "Please wait while we process your payment..." } + + script { + (PreEscaped(format!( + r#" + function submitCollectionReference(collectionReference) {{ + var redirectPathname = window.location.pathname.replace(/payments\/redirect\/(\w+)\/(\w+)\/\w+/, "payments/$1/$2/redirect/complete/worldpay"); + var redirectUrl = window.location.origin + redirectPathname; + try {{ + if (typeof collectionReference === "string" && collectionReference.length > 0) {{ + var form = document.createElement("form"); + form.action = redirectPathname; + form.method = "GET"; + var input = document.createElement("input"); + input.type = "hidden"; + input.name = "collectionReference"; + input.value = collectionReference; + form.appendChild(input); + document.body.appendChild(form); + form.submit();; + }} else {{ + window.location.replace(redirectUrl); + }} + }} catch (error) {{ + window.location.replace(redirectUrl); + }} + }} + var allowedHost = "{}"; + var collectionField = "{}"; + window.addEventListener("message", function(event) {{ + if (event.origin === allowedHost) {{ + try {{ + var data = JSON.parse(event.data); + if (collectionField.length > 0) {{ + var collectionReference = data[collectionField]; + return submitCollectionReference(collectionReference); + }} else {{ + console.error("Collection field not found in event data (" + collectionField + ")"); + }} + }} catch (error) {{ + console.error("Error parsing event data: ", error); + }} + }} else {{ + console.error("Invalid origin: " + event.origin, "Expected origin: " + allowedHost); + }} + + submitCollectionReference(""); + }}); + + // Redirect within 8 seconds if no collection reference is received + window.setTimeout(submitCollectionReference, 8000); + "#, + endpoint.host_str().map_or(endpoint.as_ref().split('/').take(3).collect::>().join("/"), |host| format!("{}://{}", endpoint.scheme(), host)), + collection_id.clone().unwrap_or("".to_string()))) + ) + } + + iframe + style="display: none;" + srcdoc=( + maud::html! { + (maud::DOCTYPE) + html { + body { + form action=(PreEscaped(endpoint.to_string())) method=(method.to_string()) #payment_form { + @for (field, value) in form_fields { + input type="hidden" name=(field) value=(value); + } + } + (PreEscaped(format!(r#" + + "#))) + } + } + }.into_string() + ) + {} + } + } + }, } } diff --git a/crates/router/src/services/api/client.rs b/crates/router/src/services/api/client.rs index e1f113633a82..9a496c18d9a6 100644 --- a/crates/router/src/services/api/client.rs +++ b/crates/router/src/services/api/client.rs @@ -403,9 +403,7 @@ impl ApiClient for ProxyClient { fn add_flow_name(&mut self, _flow_name: String) {} } -/// /// Api client for testing sending request -/// #[derive(Clone)] pub struct MockApiClient; diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index 225d6a783fc4..99800b555123 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use actix_web::http::header::HeaderMap; #[cfg(all( any(feature = "v2", feature = "v1"), @@ -10,8 +12,12 @@ use api_models::payment_methods::PaymentMethodIntentConfirm; use api_models::payouts; use api_models::{payment_methods::PaymentMethodListRequest, payments}; use async_trait::async_trait; -use common_enums::{EntityType, TokenPurpose}; +use common_enums::TokenPurpose; +#[cfg(feature = "v2")] +use common_utils::fp_utils; use common_utils::{date_time, id_type}; +#[cfg(feature = "v2")] +use diesel_models::ephemeral_key; use error_stack::{report, ResultExt}; use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; use masking::PeekInterface; @@ -19,8 +25,10 @@ use router_env::logger; use serde::Serialize; use self::blacklist::BlackList; +#[cfg(all(feature = "partial-auth", feature = "v1"))] +use self::detached::ExtractedPayload; #[cfg(feature = "partial-auth")] -use self::detached::{ExtractedPayload, GetAuthType}; +use self::detached::GetAuthType; use super::authorization::{self, permissions::Permission}; #[cfg(feature = "olap")] use super::jwt; @@ -30,7 +38,7 @@ use crate::configs::Settings; use crate::consts; #[cfg(feature = "olap")] use crate::core::errors::UserResult; -#[cfg(feature = "partial-auth")] +#[cfg(all(feature = "partial-auth", feature = "v1"))] use crate::core::metrics; use crate::{ core::{ @@ -51,13 +59,30 @@ pub mod decision; #[cfg(feature = "partial-auth")] mod detached; +#[cfg(feature = "v1")] #[derive(Clone, Debug)] pub struct AuthenticationData { pub merchant_account: domain::MerchantAccount, + pub platform_merchant_account: Option, pub key_store: domain::MerchantKeyStore, pub profile_id: Option, } +#[cfg(feature = "v2")] +#[derive(Clone, Debug)] +pub struct AuthenticationData { + pub merchant_account: domain::MerchantAccount, + pub key_store: domain::MerchantKeyStore, + pub profile: domain::Profile, + pub platform_merchant_account: Option, +} + +#[derive(Clone, Debug)] +pub struct AuthenticationDataWithoutProfile { + pub merchant_account: domain::MerchantAccount, + pub key_store: domain::MerchantKeyStore, +} + #[derive(Clone, Debug)] pub struct AuthenticationDataWithMultipleProfiles { pub merchant_account: domain::MerchantAccount, @@ -70,7 +95,13 @@ pub struct AuthenticationDataWithUser { pub merchant_account: domain::MerchantAccount, pub key_store: domain::MerchantKeyStore, pub user: storage::User, - pub profile_id: Option, + pub profile_id: id_type::ProfileId, +} + +#[derive(Clone)] +pub struct UserFromTokenWithRoleInfo { + pub user: UserFromToken, + pub role_info: authorization::roles::RoleInfo, } #[derive(Clone, Debug, Eq, PartialEq, Serialize)] @@ -82,7 +113,7 @@ pub struct AuthenticationDataWithUser { pub enum AuthenticationType { ApiKey { merchant_id: id_type::MerchantId, - key_id: String, + key_id: id_type::ApiKeyId, }, AdminApiKey, AdminApiAuthWithMerchantId { @@ -168,6 +199,7 @@ pub struct UserFromSinglePurposeToken { pub user_id: String, pub origin: domain::Origin, pub path: Vec, + pub tenant_id: Option, } #[cfg(feature = "olap")] @@ -178,6 +210,7 @@ pub struct SinglePurposeToken { pub origin: domain::Origin, pub path: Vec, pub exp: u64, + pub tenant_id: Option, } #[cfg(feature = "olap")] @@ -188,6 +221,7 @@ impl SinglePurposeToken { origin: domain::Origin, settings: &Settings, path: Vec, + tenant_id: Option, ) -> UserResult { let exp_duration = std::time::Duration::from_secs(consts::SINGLE_PURPOSE_TOKEN_TIME_IN_SECS); @@ -198,6 +232,7 @@ impl SinglePurposeToken { origin, exp, path, + tenant_id, }; jwt::generate_jwt(&token_payload, settings).await } @@ -210,7 +245,8 @@ pub struct AuthToken { pub role_id: String, pub exp: u64, pub org_id: id_type::OrganizationId, - pub profile_id: Option, + pub profile_id: id_type::ProfileId, + pub tenant_id: Option, } #[cfg(feature = "olap")] @@ -221,7 +257,8 @@ impl AuthToken { role_id: String, settings: &Settings, org_id: id_type::OrganizationId, - profile_id: Option, + profile_id: id_type::ProfileId, + tenant_id: Option, ) -> UserResult { let exp_duration = std::time::Duration::from_secs(consts::JWT_TOKEN_TIME_IN_SECS); let exp = jwt::generate_exp(exp_duration)?.as_secs(); @@ -232,6 +269,7 @@ impl AuthToken { exp, org_id, profile_id, + tenant_id, }; jwt::generate_jwt(&token_payload, settings).await } @@ -243,11 +281,13 @@ pub struct UserFromToken { pub merchant_id: id_type::MerchantId, pub role_id: String, pub org_id: id_type::OrganizationId, - pub profile_id: Option, + pub profile_id: id_type::ProfileId, + pub tenant_id: Option, } pub struct UserIdFromAuth { pub user_id: String, + pub tenant_id: Option, } #[cfg(feature = "olap")] @@ -257,6 +297,7 @@ pub struct SinglePurposeOrLoginToken { pub role_id: Option, pub purpose: Option, pub exp: u64, + pub tenant_id: Option, } pub trait AuthInfo { @@ -269,6 +310,14 @@ impl AuthInfo for () { } } +#[cfg(feature = "v1")] +impl AuthInfo for AuthenticationData { + fn get_merchant_id(&self) -> Option<&id_type::MerchantId> { + Some(self.merchant_account.get_id()) + } +} + +#[cfg(feature = "v2")] impl AuthInfo for AuthenticationData { fn get_merchant_id(&self) -> Option<&id_type::MerchantId> { Some(self.merchant_account.get_id()) @@ -349,6 +398,7 @@ where } } +#[cfg(feature = "v2")] #[async_trait] impl AuthenticateAndFetch for ApiKeyAuth where @@ -367,6 +417,9 @@ where .attach_printable("API key is empty"); } + let profile_id = HeaderMapStruct::new(request_headers) + .get_id_type_from_header::(headers::X_PROFILE_ID)?; + let api_key = api_keys::PlaintextApiKey::from(api_key); let hash_key = { let config = state.conf(); @@ -415,10 +468,153 @@ where .await .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + // Get connected merchant account if API call is done by Platform merchant account on behalf of connected merchant account + let (merchant, platform_merchant_account) = if state.conf().platform.enabled { + get_platform_merchant_account(state, request_headers, merchant).await? + } else { + (merchant, None) + }; + + let key_store = if platform_merchant_account.is_some() { + state + .store() + .get_merchant_key_store_by_merchant_id( + key_manager_state, + merchant.get_id(), + &state.store().get_master_key().to_vec().into(), + ) + .await + .change_context(errors::ApiErrorResponse::Unauthorized) + .attach_printable("Failed to fetch merchant key store for the merchant id")? + } else { + key_store + }; + + let profile = state + .store() + .find_business_profile_by_profile_id(key_manager_state, &key_store, &profile_id) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + let auth = AuthenticationData { merchant_account: merchant, + platform_merchant_account, key_store, - profile_id: None, + profile, + }; + Ok(( + auth.clone(), + AuthenticationType::ApiKey { + merchant_id: auth.merchant_account.get_id().clone(), + key_id: stored_api_key.key_id, + }, + )) + } +} + +#[cfg(feature = "v1")] +#[async_trait] +impl AuthenticateAndFetch for ApiKeyAuth +where + A: SessionStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(AuthenticationData, AuthenticationType)> { + let api_key = get_api_key(request_headers) + .change_context(errors::ApiErrorResponse::Unauthorized)? + .trim(); + if api_key.is_empty() { + return Err(errors::ApiErrorResponse::Unauthorized) + .attach_printable("API key is empty"); + } + + let api_key = api_keys::PlaintextApiKey::from(api_key); + let hash_key = { + let config = state.conf(); + config.api_keys.get_inner().get_hash_key()? + }; + let hashed_api_key = api_key.keyed_hash(hash_key.peek()); + + let stored_api_key = state + .store() + .find_api_key_by_hash_optional(hashed_api_key.into()) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) // If retrieve failed + .attach_printable("Failed to retrieve API key")? + .ok_or(report!(errors::ApiErrorResponse::Unauthorized)) // If retrieve returned `None` + .attach_printable("Merchant not authenticated")?; + + if stored_api_key + .expires_at + .map(|expires_at| expires_at < date_time::now()) + .unwrap_or(false) + { + return Err(report!(errors::ApiErrorResponse::Unauthorized)) + .attach_printable("API key has expired"); + } + + let key_manager_state = &(&state.session_state()).into(); + + let key_store = state + .store() + .get_merchant_key_store_by_merchant_id( + key_manager_state, + &stored_api_key.merchant_id, + &state.store().get_master_key().to_vec().into(), + ) + .await + .change_context(errors::ApiErrorResponse::Unauthorized) + .attach_printable("Failed to fetch merchant key store for the merchant id")?; + + let profile_id = + get_header_value_by_key(headers::X_PROFILE_ID.to_string(), request_headers)? + .map(id_type::ProfileId::from_str) + .transpose() + .change_context(errors::ValidationError::IncorrectValueProvided { + field_name: "X-Profile-Id", + }) + .change_context(errors::ApiErrorResponse::Unauthorized)?; + + let merchant = state + .store() + .find_merchant_account_by_merchant_id( + key_manager_state, + &stored_api_key.merchant_id, + &key_store, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + + // Get connected merchant account if API call is done by Platform merchant account on behalf of connected merchant account + let (merchant, platform_merchant_account) = if state.conf().platform.enabled { + get_platform_merchant_account(state, request_headers, merchant).await? + } else { + (merchant, None) + }; + + let key_store = if platform_merchant_account.is_some() { + state + .store() + .get_merchant_key_store_by_merchant_id( + key_manager_state, + merchant.get_id(), + &state.store().get_master_key().to_vec().into(), + ) + .await + .change_context(errors::ApiErrorResponse::Unauthorized) + .attach_printable("Failed to fetch merchant key store for the merchant id")? + } else { + key_store + }; + + let auth = AuthenticationData { + merchant_account: merchant, + platform_merchant_account, + key_store, + profile_id, }; Ok(( auth.clone(), @@ -446,7 +642,7 @@ where } } -#[cfg(feature = "partial-auth")] +#[cfg(all(feature = "partial-auth", feature = "v1"))] #[async_trait] impl AuthenticateAndFetch for HeaderAuth where @@ -467,9 +663,16 @@ where } let report_failure = || { - metrics::PARTIAL_AUTH_FAILURE.add(&metrics::CONTEXT, 1, &[]); + metrics::PARTIAL_AUTH_FAILURE.add(1, &[]); }; + let profile_id = HeaderMapStruct::new(request_headers) + .get_id_type_from_header_if_present::(headers::X_PROFILE_ID) + .change_context(errors::ValidationError::IncorrectValueProvided { + field_name: "X-Profile-Id", + }) + .change_context(errors::ApiErrorResponse::Unauthorized)?; + let payload = ExtractedPayload::from_headers(request_headers) .and_then(|value| { let (algo, secret) = state.get_detached_auth()?; @@ -491,7 +694,13 @@ where merchant_id: Some(merchant_id), key_id: Some(key_id), } => { - let auth = construct_authentication_data(state, &merchant_id).await?; + let auth = construct_authentication_data( + state, + &merchant_id, + request_headers, + profile_id, + ) + .await?; Ok(( auth.clone(), AuthenticationType::ApiKey { @@ -505,7 +714,13 @@ where merchant_id: Some(merchant_id), key_id: None, } => { - let auth = construct_authentication_data(state, &merchant_id).await?; + let auth = construct_authentication_data( + state, + &merchant_id, + request_headers, + profile_id, + ) + .await?; Ok(( auth.clone(), AuthenticationType::PublishableKey { @@ -531,13 +746,60 @@ where } } -#[cfg(feature = "partial-auth")] +#[cfg(all(feature = "partial-auth", feature = "v2"))] +#[async_trait] +impl AuthenticateAndFetch for HeaderAuth +where + A: SessionStateInfo + Sync, + I: AuthenticateAndFetch + + AuthenticateAndFetch + + GetAuthType + + Sync + + Send, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(AuthenticationData, AuthenticationType)> { + let (auth_data, auth_type): (AuthenticationData, AuthenticationType) = self + .0 + .authenticate_and_fetch(request_headers, state) + .await?; + + let profile_id = HeaderMapStruct::new(request_headers) + .get_id_type_from_header::(headers::X_PROFILE_ID)?; + + let key_manager_state = &(&state.session_state()).into(); + let profile = state + .store() + .find_business_profile_by_profile_id( + key_manager_state, + &auth_data.key_store, + &profile_id, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + + let auth_data_v2 = AuthenticationData { + merchant_account: auth_data.merchant_account, + platform_merchant_account: auth_data.platform_merchant_account, + key_store: auth_data.key_store, + profile, + }; + Ok((auth_data_v2, auth_type)) + } +} + +#[cfg(all(feature = "partial-auth", feature = "v1"))] async fn construct_authentication_data( state: &A, merchant_id: &id_type::MerchantId, + request_headers: &HeaderMap, + profile_id: Option, ) -> RouterResult where - A: SessionStateInfo, + A: SessionStateInfo + Sync, { let key_store = state .store() @@ -560,10 +822,33 @@ where .await .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + // Get connected merchant account if API call is done by Platform merchant account on behalf of connected merchant account + let (merchant, platform_merchant_account) = if state.conf().platform.enabled { + get_platform_merchant_account(state, request_headers, merchant).await? + } else { + (merchant, None) + }; + + let key_store = if platform_merchant_account.is_some() { + state + .store() + .get_merchant_key_store_by_merchant_id( + &(&state.session_state()).into(), + merchant.get_id(), + &state.store().get_master_key().to_vec().into(), + ) + .await + .change_context(errors::ApiErrorResponse::Unauthorized) + .attach_printable("Failed to fetch merchant key store for the merchant id")? + } else { + key_store + }; + let auth = AuthenticationData { merchant_account: merchant, + platform_merchant_account, key_store, - profile_id: None, + profile_id, }; Ok(auth) @@ -588,6 +873,10 @@ where if payload.check_in_blacklist(state).await? { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } + authorization::check_tenant( + payload.tenant_id.clone(), + &state.session_state().tenant.tenant_id, + )?; if self.0 != payload.purpose { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); @@ -598,6 +887,7 @@ where user_id: payload.user_id.clone(), origin: payload.origin.clone(), path: payload.path, + tenant_id: payload.tenant_id, }, AuthenticationType::SinglePurposeJwt { user_id: payload.user_id, @@ -622,6 +912,10 @@ where if payload.check_in_blacklist(state).await? { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } + authorization::check_tenant( + payload.tenant_id.clone(), + &state.session_state().tenant.tenant_id, + )?; if self.0 != payload.purpose { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); @@ -632,6 +926,7 @@ where user_id: payload.user_id.clone(), origin: payload.origin.clone(), path: payload.path, + tenant_id: payload.tenant_id, }), AuthenticationType::SinglePurposeJwt { user_id: payload.user_id, @@ -661,6 +956,10 @@ where if payload.check_in_blacklist(state).await? { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } + authorization::check_tenant( + payload.tenant_id.clone(), + &state.session_state().tenant.tenant_id, + )?; let is_purpose_equal = payload .purpose @@ -674,6 +973,49 @@ where Ok(( UserIdFromAuth { user_id: payload.user_id.clone(), + tenant_id: payload.tenant_id, + }, + AuthenticationType::SinglePurposeOrLoginJwt { + user_id: payload.user_id, + purpose: payload.purpose, + role_id: payload.role_id, + }, + )) + } else { + Err(errors::ApiErrorResponse::InvalidJwtToken.into()) + } + } +} + +#[cfg(feature = "olap")] +#[derive(Debug)] +pub struct AnyPurposeOrLoginTokenAuth; + +#[cfg(feature = "olap")] +#[async_trait] +impl AuthenticateAndFetch for AnyPurposeOrLoginTokenAuth +where + A: SessionStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(UserIdFromAuth, AuthenticationType)> { + let payload = + parse_jwt_payload::(request_headers, state).await?; + if payload.check_in_blacklist(state).await? { + return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); + } + + let purpose_exists = payload.purpose.is_some(); + let role_id_exists = payload.role_id.is_some(); + + if purpose_exists ^ role_id_exists { + Ok(( + UserIdFromAuth { + user_id: payload.user_id.clone(), + tenant_id: payload.tenant_id, }, AuthenticationType::SinglePurposeOrLoginJwt { user_id: payload.user_id, @@ -718,6 +1060,7 @@ where #[derive(Debug)] pub struct AdminApiAuthWithMerchantIdFromRoute(pub id_type::MerchantId); +#[cfg(feature = "v1")] #[async_trait] impl AuthenticateAndFetch for AdminApiAuthWithMerchantIdFromRoute where @@ -743,30 +1086,17 @@ where &state.store().get_master_key().to_vec().into(), ) .await - .map_err(|e| { - if e.current_context().is_db_not_found() { - e.change_context(errors::ApiErrorResponse::Unauthorized) - } else { - e.change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to fetch merchant key store for the merchant id") - } - })?; + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; let merchant = state .store() .find_merchant_account_by_merchant_id(key_manager_state, &merchant_id, &key_store) .await - .map_err(|e| { - if e.current_context().is_db_not_found() { - e.change_context(errors::ApiErrorResponse::Unauthorized) - } else { - e.change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to fetch merchant account for the merchant id") - } - })?; + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; let auth = AuthenticationData { merchant_account: merchant, + platform_merchant_account: None, key_store, profile_id: None, }; @@ -778,29 +1108,143 @@ where } } -/// A helper struct to extract headers from the request -struct HeaderMapStruct<'a> { - headers: &'a HeaderMap, -} +#[cfg(feature = "v2")] +#[async_trait] +impl AuthenticateAndFetch for AdminApiAuthWithMerchantIdFromRoute +where + A: SessionStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(AuthenticationData, AuthenticationType)> { + if state.conf().platform.enabled { + throw_error_if_platform_merchant_authentication_required(request_headers)?; + } -impl<'a> HeaderMapStruct<'a> { - pub fn new(headers: &'a HeaderMap) -> Self { - HeaderMapStruct { headers } - } + AdminApiAuth + .authenticate_and_fetch(request_headers, state) + .await?; - fn get_mandatory_header_value_by_key( - &self, - key: String, - ) -> Result<&str, error_stack::Report> { - self.headers - .get(&key) + let merchant_id = self.0.clone(); + let profile_id = + get_id_type_by_key_from_headers(headers::X_PROFILE_ID.to_string(), request_headers)? + .get_required_value(headers::X_PROFILE_ID)?; + let key_manager_state = &(&state.session_state()).into(); + let key_store = state + .store() + .get_merchant_key_store_by_merchant_id( + key_manager_state, + &merchant_id, + &state.store().get_master_key().to_vec().into(), + ) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + let profile = state + .store() + .find_business_profile_by_merchant_id_profile_id( + key_manager_state, + &key_store, + &merchant_id, + &profile_id, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + let merchant = state + .store() + .find_merchant_account_by_merchant_id(key_manager_state, &merchant_id, &key_store) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + + let auth = AuthenticationData { + merchant_account: merchant, + key_store, + profile, + platform_merchant_account: None, + }; + + Ok(( + auth, + AuthenticationType::AdminApiAuthWithMerchantId { merchant_id }, + )) + } +} + +#[cfg(feature = "v2")] +#[async_trait] +impl AuthenticateAndFetch + for AdminApiAuthWithMerchantIdFromRoute +where + A: SessionStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(AuthenticationDataWithoutProfile, AuthenticationType)> { + if state.conf().platform.enabled { + throw_error_if_platform_merchant_authentication_required(request_headers)?; + } + + AdminApiAuth + .authenticate_and_fetch(request_headers, state) + .await?; + + let merchant_id = self.0.clone(); + + let key_manager_state = &(&state.session_state()).into(); + let key_store = state + .store() + .get_merchant_key_store_by_merchant_id( + key_manager_state, + &merchant_id, + &state.store().get_master_key().to_vec().into(), + ) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + + let merchant = state + .store() + .find_merchant_account_by_merchant_id(key_manager_state, &merchant_id, &key_store) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + + let auth = AuthenticationDataWithoutProfile { + merchant_account: merchant, + key_store, + }; + + Ok(( + auth, + AuthenticationType::AdminApiAuthWithMerchantId { merchant_id }, + )) + } +} + +/// A helper struct to extract headers from the request +pub(crate) struct HeaderMapStruct<'a> { + headers: &'a HeaderMap, +} + +impl<'a> HeaderMapStruct<'a> { + pub fn new(headers: &'a HeaderMap) -> Self { + HeaderMapStruct { headers } + } + + fn get_mandatory_header_value_by_key( + &self, + key: &str, + ) -> Result<&str, error_stack::Report> { + self.headers + .get(key) .ok_or(errors::ApiErrorResponse::InvalidRequestData { message: format!("Missing header key: `{}`", key), }) .attach_printable(format!("Failed to find header key: {}", key))? .to_str() .change_context(errors::ApiErrorResponse::InvalidDataValue { - field_name: "`X-Merchant-Id` in headers", + field_name: "`{key}` in headers", }) .attach_printable(format!( "Failed to convert header value to string for header key: {}", @@ -808,16 +1252,66 @@ impl<'a> HeaderMapStruct<'a> { )) } - pub fn get_merchant_id_from_header(&self) -> RouterResult { - self.get_mandatory_header_value_by_key(headers::X_MERCHANT_ID.into()) + /// Get the id type from the header + /// This can be used to extract lineage ids from the headers + pub fn get_id_type_from_header< + T: TryFrom< + std::borrow::Cow<'static, str>, + Error = error_stack::Report, + >, + >( + &self, + key: &str, + ) -> RouterResult { + self.get_mandatory_header_value_by_key(key) + .map(|val| val.to_owned()) + .and_then(|header_value| { + T::try_from(std::borrow::Cow::Owned(header_value)).change_context( + errors::ApiErrorResponse::InvalidRequestData { + message: format!("`{}` header is invalid", key), + }, + ) + }) + } + #[cfg(feature = "v2")] + pub fn get_organization_id_from_header(&self) -> RouterResult { + self.get_mandatory_header_value_by_key(headers::X_ORGANIZATION_ID) .map(|val| val.to_owned()) - .and_then(|merchant_id| { - id_type::MerchantId::wrap(merchant_id).change_context( + .and_then(|organization_id| { + id_type::OrganizationId::try_from_string(organization_id).change_context( + errors::ApiErrorResponse::InvalidRequestData { + message: format!("`{}` header is invalid", headers::X_ORGANIZATION_ID), + }, + ) + }) + } + + pub fn get_id_type_from_header_if_present(&self, key: &str) -> RouterResult> + where + T: TryFrom< + std::borrow::Cow<'static, str>, + Error = error_stack::Report, + >, + { + self.headers + .get(key) + .map(|value| value.to_str()) + .transpose() + .change_context(errors::ApiErrorResponse::InvalidDataValue { + field_name: "`{key}` in headers", + }) + .attach_printable(format!( + "Failed to convert header value to string for header key: {}", + key + ))? + .map(|value| { + T::try_from(std::borrow::Cow::Owned(value.to_owned())).change_context( errors::ApiErrorResponse::InvalidRequestData { - message: format!("`{}` header is invalid", headers::X_MERCHANT_ID), + message: format!("`{}` header is invalid", key), }, ) }) + .transpose() } } @@ -825,6 +1319,7 @@ impl<'a> HeaderMapStruct<'a> { #[derive(Debug)] pub struct AdminApiAuthWithMerchantIdFromHeader; +#[cfg(feature = "v1")] #[async_trait] impl AuthenticateAndFetch for AdminApiAuthWithMerchantIdFromHeader where @@ -839,7 +1334,8 @@ where .authenticate_and_fetch(request_headers, state) .await?; - let merchant_id = HeaderMapStruct::new(request_headers).get_merchant_id_from_header()?; + let merchant_id = HeaderMapStruct::new(request_headers) + .get_id_type_from_header::(headers::X_MERCHANT_ID)?; let key_manager_state = &(&state.session_state()).into(); let key_store = state @@ -850,30 +1346,17 @@ where &state.store().get_master_key().to_vec().into(), ) .await - .map_err(|e| { - if e.current_context().is_db_not_found() { - e.change_context(errors::ApiErrorResponse::MerchantAccountNotFound) - } else { - e.change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to fetch merchant key store for the merchant id") - } - })?; + .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; let merchant = state .store() .find_merchant_account_by_merchant_id(key_manager_state, &merchant_id, &key_store) .await - .map_err(|e| { - if e.current_context().is_db_not_found() { - e.change_context(errors::ApiErrorResponse::Unauthorized) - } else { - e.change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to fetch merchant account for the merchant id") - } - })?; + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; let auth = AuthenticationData { merchant_account: merchant, + platform_merchant_account: None, key_store, profile_id: None, }; @@ -884,11 +1367,9 @@ where } } -#[derive(Debug)] -pub struct EphemeralKeyAuth; - +#[cfg(feature = "v2")] #[async_trait] -impl AuthenticateAndFetch for EphemeralKeyAuth +impl AuthenticateAndFetch for AdminApiAuthWithMerchantIdFromHeader where A: SessionStateInfo + Sync, { @@ -897,88 +1378,116 @@ where request_headers: &HeaderMap, state: &A, ) -> RouterResult<(AuthenticationData, AuthenticationType)> { - let api_key = - get_api_key(request_headers).change_context(errors::ApiErrorResponse::Unauthorized)?; - let ephemeral_key = state - .store() - .get_ephemeral_key(api_key) - .await - .change_context(errors::ApiErrorResponse::Unauthorized)?; + if state.conf().platform.enabled { + throw_error_if_platform_merchant_authentication_required(request_headers)?; + } - MerchantIdAuth(ephemeral_key.merchant_id) + AdminApiAuth .authenticate_and_fetch(request_headers, state) + .await?; + + let merchant_id = HeaderMapStruct::new(request_headers) + .get_id_type_from_header::(headers::X_MERCHANT_ID)?; + let profile_id = + get_id_type_by_key_from_headers(headers::X_PROFILE_ID.to_string(), request_headers)? + .get_required_value(headers::X_PROFILE_ID)?; + + let key_manager_state = &(&state.session_state()).into(); + let key_store = state + .store() + .get_merchant_key_store_by_merchant_id( + key_manager_state, + &merchant_id, + &state.store().get_master_key().to_vec().into(), + ) + .await + .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; + let profile = state + .store() + .find_business_profile_by_merchant_id_profile_id( + key_manager_state, + &key_store, + &merchant_id, + &profile_id, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + let merchant = state + .store() + .find_merchant_account_by_merchant_id(key_manager_state, &merchant_id, &key_store) .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + + let auth = AuthenticationData { + merchant_account: merchant, + key_store, + profile, + platform_merchant_account: None, + }; + Ok(( + auth, + AuthenticationType::AdminApiAuthWithMerchantId { merchant_id }, + )) } } -#[derive(Debug)] -pub struct MerchantIdAuth(pub id_type::MerchantId); +#[cfg(feature = "v2")] #[async_trait] -impl AuthenticateAndFetch for MerchantIdAuth +impl AuthenticateAndFetch + for AdminApiAuthWithMerchantIdFromHeader where A: SessionStateInfo + Sync, { async fn authenticate_and_fetch( &self, - _request_headers: &HeaderMap, + request_headers: &HeaderMap, state: &A, - ) -> RouterResult<(AuthenticationData, AuthenticationType)> { + ) -> RouterResult<(AuthenticationDataWithoutProfile, AuthenticationType)> { + if state.conf().platform.enabled { + throw_error_if_platform_merchant_authentication_required(request_headers)?; + } + + AdminApiAuth + .authenticate_and_fetch(request_headers, state) + .await?; + + let merchant_id = HeaderMapStruct::new(request_headers) + .get_id_type_from_header::(headers::X_MERCHANT_ID)?; + let key_manager_state = &(&state.session_state()).into(); let key_store = state .store() .get_merchant_key_store_by_merchant_id( key_manager_state, - &self.0, + &merchant_id, &state.store().get_master_key().to_vec().into(), ) .await - .map_err(|e| { - if e.current_context().is_db_not_found() { - e.change_context(errors::ApiErrorResponse::Unauthorized) - } else { - e.change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to fetch merchant key store for the merchant id") - } - })?; + .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?; let merchant = state .store() - .find_merchant_account_by_merchant_id(key_manager_state, &self.0, &key_store) + .find_merchant_account_by_merchant_id(key_manager_state, &merchant_id, &key_store) .await - .map_err(|e| { - if e.current_context().is_db_not_found() { - e.change_context(errors::ApiErrorResponse::Unauthorized) - } else { - e.change_context(errors::ApiErrorResponse::InternalServerError) - } - })?; + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; - let auth = AuthenticationData { + let auth = AuthenticationDataWithoutProfile { merchant_account: merchant, key_store, - profile_id: None, }; Ok(( - auth.clone(), - AuthenticationType::MerchantId { - merchant_id: auth.merchant_account.get_id().clone(), - }, + auth, + AuthenticationType::AdminApiAuthWithMerchantId { merchant_id }, )) } } #[derive(Debug)] -pub struct PublishableKeyAuth; - -#[cfg(feature = "partial-auth")] -impl GetAuthType for PublishableKeyAuth { - fn get_auth_type(&self) -> detached::PayloadType { - detached::PayloadType::PublishableKey - } -} +pub struct EphemeralKeyAuth; +#[cfg(feature = "v1")] #[async_trait] -impl AuthenticateAndFetch for PublishableKeyAuth +impl AuthenticateAndFetch for EphemeralKeyAuth where A: SessionStateInfo + Sync, { @@ -987,39 +1496,763 @@ where request_headers: &HeaderMap, state: &A, ) -> RouterResult<(AuthenticationData, AuthenticationType)> { - let publishable_key = + let api_key = get_api_key(request_headers).change_context(errors::ApiErrorResponse::Unauthorized)?; - let key_manager_state = &(&state.session_state()).into(); - state + let ephemeral_key = state .store() - .find_merchant_account_by_publishable_key(key_manager_state, publishable_key) + .get_ephemeral_key(api_key) + .await + .change_context(errors::ApiErrorResponse::Unauthorized)?; + + MerchantIdAuth(ephemeral_key.merchant_id) + .authenticate_and_fetch(request_headers, state) .await - .map_err(|e| { - if e.current_context().is_db_not_found() { - e.change_context(errors::ApiErrorResponse::Unauthorized) - } else { - e.change_context(errors::ApiErrorResponse::InternalServerError) - } - }) - .map(|auth| { - ( - auth.clone(), - AuthenticationType::PublishableKey { - merchant_id: auth.merchant_account.get_id().clone(), - }, - ) - }) } } -#[derive(Debug)] -pub(crate) struct JWTAuth { - pub permission: Permission, - pub minimum_entity_level: EntityType, +#[cfg(feature = "v2")] +#[async_trait] +impl AuthenticateAndFetch for EphemeralKeyAuth +where + A: SessionStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(AuthenticationData, AuthenticationType)> { + let api_key = + get_api_key(request_headers).change_context(errors::ApiErrorResponse::Unauthorized)?; + let ephemeral_key = state + .store() + .get_ephemeral_key(api_key) + .await + .change_context(errors::ApiErrorResponse::Unauthorized)?; + + let resource_type = HeaderMapStruct::new(request_headers) + .get_mandatory_header_value_by_key(headers::X_RESOURCE_TYPE) + .and_then(|val| { + ephemeral_key::ResourceType::from_str(val).change_context( + errors::ApiErrorResponse::InvalidRequestData { + message: format!("`{}` header is invalid", headers::X_RESOURCE_TYPE), + }, + ) + })?; + + fp_utils::when(resource_type != ephemeral_key.resource_type, || { + Err(errors::ApiErrorResponse::Unauthorized) + })?; + + MerchantIdAuth(ephemeral_key.merchant_id) + .authenticate_and_fetch(request_headers, state) + .await + } +} +#[derive(Debug)] +pub struct MerchantIdAuth(pub id_type::MerchantId); + +#[cfg(feature = "v1")] +#[async_trait] +impl AuthenticateAndFetch for MerchantIdAuth +where + A: SessionStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(AuthenticationData, AuthenticationType)> { + if state.conf().platform.enabled { + throw_error_if_platform_merchant_authentication_required(request_headers)?; + } + + let key_manager_state = &(&state.session_state()).into(); + let key_store = state + .store() + .get_merchant_key_store_by_merchant_id( + key_manager_state, + &self.0, + &state.store().get_master_key().to_vec().into(), + ) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + + let merchant = state + .store() + .find_merchant_account_by_merchant_id(key_manager_state, &self.0, &key_store) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + + let auth = AuthenticationData { + merchant_account: merchant, + platform_merchant_account: None, + key_store, + profile_id: None, + }; + Ok(( + auth.clone(), + AuthenticationType::MerchantId { + merchant_id: auth.merchant_account.get_id().clone(), + }, + )) + } +} + +#[cfg(feature = "v2")] +#[async_trait] +impl AuthenticateAndFetch for MerchantIdAuth +where + A: SessionStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(AuthenticationData, AuthenticationType)> { + if state.conf().platform.enabled { + throw_error_if_platform_merchant_authentication_required(request_headers)?; + } + + let key_manager_state = &(&state.session_state()).into(); + let profile_id = + get_id_type_by_key_from_headers(headers::X_PROFILE_ID.to_string(), request_headers)? + .get_required_value(headers::X_PROFILE_ID)?; + let key_store = state + .store() + .get_merchant_key_store_by_merchant_id( + key_manager_state, + &self.0, + &state.store().get_master_key().to_vec().into(), + ) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + + let profile = state + .store() + .find_business_profile_by_merchant_id_profile_id( + key_manager_state, + &key_store, + &self.0, + &profile_id, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + let merchant = state + .store() + .find_merchant_account_by_merchant_id(key_manager_state, &self.0, &key_store) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + + let auth = AuthenticationData { + merchant_account: merchant, + key_store, + profile, + platform_merchant_account: None, + }; + Ok(( + auth.clone(), + AuthenticationType::MerchantId { + merchant_id: auth.merchant_account.get_id().clone(), + }, + )) + } +} + +#[derive(Debug)] +#[cfg(feature = "v2")] +pub struct MerchantIdAndProfileIdAuth { + pub merchant_id: id_type::MerchantId, + pub profile_id: id_type::ProfileId, +} + +#[cfg(feature = "v2")] +#[async_trait] +impl AuthenticateAndFetch for MerchantIdAndProfileIdAuth +where + A: SessionStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(AuthenticationData, AuthenticationType)> { + if state.conf().platform.enabled { + throw_error_if_platform_merchant_authentication_required(request_headers)?; + } + + let key_manager_state = &(&state.session_state()).into(); + let key_store = state + .store() + .get_merchant_key_store_by_merchant_id( + key_manager_state, + &self.merchant_id, + &state.store().get_master_key().to_vec().into(), + ) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + + let profile = state + .store() + .find_business_profile_by_merchant_id_profile_id( + key_manager_state, + &key_store, + &self.merchant_id, + &self.profile_id, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + let merchant = state + .store() + .find_merchant_account_by_merchant_id(key_manager_state, &self.merchant_id, &key_store) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + + let auth = AuthenticationData { + merchant_account: merchant, + key_store, + profile, + platform_merchant_account: None, + }; + Ok(( + auth.clone(), + AuthenticationType::MerchantId { + merchant_id: auth.merchant_account.get_id().clone(), + }, + )) + } +} + +#[derive(Debug)] +#[cfg(feature = "v2")] +pub struct PublishableKeyAndProfileIdAuth { + pub publishable_key: String, + pub profile_id: id_type::ProfileId, +} + +#[async_trait] +#[cfg(feature = "v2")] +impl AuthenticateAndFetch for PublishableKeyAndProfileIdAuth +where + A: SessionStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + _request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(AuthenticationData, AuthenticationType)> { + let key_manager_state = &(&state.session_state()).into(); + let (merchant_account, key_store) = state + .store() + .find_merchant_account_by_publishable_key( + key_manager_state, + self.publishable_key.as_str(), + ) + .await + .map_err(|e| { + if e.current_context().is_db_not_found() { + e.change_context(errors::ApiErrorResponse::Unauthorized) + } else { + e.change_context(errors::ApiErrorResponse::InternalServerError) + } + })?; + + let profile = state + .store() + .find_business_profile_by_profile_id(key_manager_state, &key_store, &self.profile_id) + .await + .to_not_found_response(errors::ApiErrorResponse::ProfileNotFound { + id: self.profile_id.get_string_repr().to_owned(), + })?; + + let merchant_id = merchant_account.get_id().clone(); + + Ok(( + AuthenticationData { + merchant_account, + key_store, + profile, + platform_merchant_account: None, + }, + AuthenticationType::PublishableKey { merchant_id }, + )) + } +} + +#[derive(Debug)] +pub struct PublishableKeyAuth; + +#[cfg(feature = "partial-auth")] +impl GetAuthType for PublishableKeyAuth { + fn get_auth_type(&self) -> detached::PayloadType { + detached::PayloadType::PublishableKey + } +} + +#[cfg(feature = "v1")] +#[async_trait] +impl AuthenticateAndFetch for PublishableKeyAuth +where + A: SessionStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(AuthenticationData, AuthenticationType)> { + if state.conf().platform.enabled { + throw_error_if_platform_merchant_authentication_required(request_headers)?; + } + + let publishable_key = + get_api_key(request_headers).change_context(errors::ApiErrorResponse::Unauthorized)?; + let key_manager_state = &(&state.session_state()).into(); + state + .store() + .find_merchant_account_by_publishable_key(key_manager_state, publishable_key) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized) + .map(|(merchant_account, key_store)| { + let merchant_id = merchant_account.get_id().clone(); + ( + AuthenticationData { + merchant_account, + platform_merchant_account: None, + key_store, + profile_id: None, + }, + AuthenticationType::PublishableKey { merchant_id }, + ) + }) + } +} + +#[cfg(feature = "v2")] +#[async_trait] +impl AuthenticateAndFetch for PublishableKeyAuth +where + A: SessionStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(AuthenticationData, AuthenticationType)> { + let publishable_key = + get_api_key(request_headers).change_context(errors::ApiErrorResponse::Unauthorized)?; + let key_manager_state = &(&state.session_state()).into(); + let profile_id = + get_id_type_by_key_from_headers(headers::X_PROFILE_ID.to_string(), request_headers)? + .get_required_value(headers::X_PROFILE_ID)?; + + let (merchant_account, key_store) = state + .store() + .find_merchant_account_by_publishable_key(key_manager_state, publishable_key) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + let merchant_id = merchant_account.get_id().clone(); + let profile = state + .store() + .find_business_profile_by_merchant_id_profile_id( + key_manager_state, + &key_store, + &merchant_id, + &profile_id, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + Ok(( + AuthenticationData { + merchant_account, + key_store, + profile, + platform_merchant_account: None, + }, + AuthenticationType::PublishableKey { merchant_id }, + )) + } +} + +#[derive(Debug)] +pub(crate) struct JWTAuth { + pub permission: Permission, +} + +#[async_trait] +impl AuthenticateAndFetch<(), A> for JWTAuth +where + A: SessionStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<((), AuthenticationType)> { + let payload = parse_jwt_payload::(request_headers, state).await?; + if payload.check_in_blacklist(state).await? { + return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); + } + + authorization::check_tenant( + payload.tenant_id.clone(), + &state.session_state().tenant.tenant_id, + )?; + + let role_info = authorization::get_role_info(state, &payload).await?; + authorization::check_permission(self.permission, &role_info)?; + + Ok(( + (), + AuthenticationType::MerchantJwt { + merchant_id: payload.merchant_id, + user_id: Some(payload.user_id), + }, + )) + } +} + +#[cfg(feature = "olap")] +#[async_trait] +impl AuthenticateAndFetch for JWTAuth +where + A: SessionStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(UserFromToken, AuthenticationType)> { + let payload = parse_jwt_payload::(request_headers, state).await?; + if payload.check_in_blacklist(state).await? { + return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); + } + authorization::check_tenant( + payload.tenant_id.clone(), + &state.session_state().tenant.tenant_id, + )?; + + let role_info = authorization::get_role_info(state, &payload).await?; + authorization::check_permission(self.permission, &role_info)?; + + Ok(( + UserFromToken { + user_id: payload.user_id.clone(), + merchant_id: payload.merchant_id.clone(), + org_id: payload.org_id, + role_id: payload.role_id, + profile_id: payload.profile_id, + tenant_id: payload.tenant_id, + }, + AuthenticationType::MerchantJwt { + merchant_id: payload.merchant_id, + user_id: Some(payload.user_id), + }, + )) + } +} + +#[cfg(feature = "olap")] +#[async_trait] +impl AuthenticateAndFetch for JWTAuth +where + A: SessionStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(AuthenticationDataWithMultipleProfiles, AuthenticationType)> { + let payload = parse_jwt_payload::(request_headers, state).await?; + if payload.check_in_blacklist(state).await? { + return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); + } + authorization::check_tenant( + payload.tenant_id.clone(), + &state.session_state().tenant.tenant_id, + )?; + + let role_info = authorization::get_role_info(state, &payload).await?; + authorization::check_permission(self.permission, &role_info)?; + + let key_manager_state = &(&state.session_state()).into(); + let key_store = state + .store() + .get_merchant_key_store_by_merchant_id( + key_manager_state, + &payload.merchant_id, + &state.store().get_master_key().to_vec().into(), + ) + .await + .change_context(errors::ApiErrorResponse::InvalidJwtToken) + .attach_printable("Failed to fetch merchant key store for the merchant id")?; + + let merchant = state + .store() + .find_merchant_account_by_merchant_id( + key_manager_state, + &payload.merchant_id, + &key_store, + ) + .await + .change_context(errors::ApiErrorResponse::InvalidJwtToken)?; + + Ok(( + AuthenticationDataWithMultipleProfiles { + key_store, + merchant_account: merchant, + profile_id_list: None, + }, + AuthenticationType::MerchantJwt { + merchant_id: payload.merchant_id, + user_id: Some(payload.user_id), + }, + )) + } +} + +pub struct JWTAuthOrganizationFromRoute { + pub organization_id: id_type::OrganizationId, + pub required_permission: Permission, +} + +#[async_trait] +impl AuthenticateAndFetch<(), A> for JWTAuthOrganizationFromRoute +where + A: SessionStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<((), AuthenticationType)> { + let payload = parse_jwt_payload::(request_headers, state).await?; + if payload.check_in_blacklist(state).await? { + return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); + } + authorization::check_tenant( + payload.tenant_id.clone(), + &state.session_state().tenant.tenant_id, + )?; + + let role_info = authorization::get_role_info(state, &payload).await?; + authorization::check_permission(self.required_permission, &role_info)?; + + // Check if token has access to Organization that has been requested in the route + if payload.org_id != self.organization_id { + return Err(report!(errors::ApiErrorResponse::InvalidJwtToken)); + } + Ok(( + (), + AuthenticationType::OrganizationJwt { + org_id: payload.org_id, + user_id: payload.user_id, + }, + )) + } +} + +pub struct JWTAuthMerchantFromRoute { + pub merchant_id: id_type::MerchantId, + pub required_permission: Permission, +} + +pub struct JWTAuthMerchantFromHeader { + pub required_permission: Permission, +} + +#[async_trait] +impl AuthenticateAndFetch<(), A> for JWTAuthMerchantFromHeader +where + A: SessionStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<((), AuthenticationType)> { + let payload = parse_jwt_payload::(request_headers, state).await?; + if payload.check_in_blacklist(state).await? { + return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); + } + authorization::check_tenant( + payload.tenant_id.clone(), + &state.session_state().tenant.tenant_id, + )?; + + let role_info = authorization::get_role_info(state, &payload).await?; + authorization::check_permission(self.required_permission, &role_info)?; + + let merchant_id_from_header = HeaderMapStruct::new(request_headers) + .get_id_type_from_header::(headers::X_MERCHANT_ID)?; + + // Check if token has access to MerchantId that has been requested through headers + if payload.merchant_id != merchant_id_from_header { + return Err(report!(errors::ApiErrorResponse::InvalidJwtToken)); + } + Ok(( + (), + AuthenticationType::MerchantJwt { + merchant_id: payload.merchant_id, + user_id: Some(payload.user_id), + }, + )) + } +} + +#[cfg(feature = "v1")] +#[async_trait] +impl AuthenticateAndFetch for JWTAuthMerchantFromHeader +where + A: SessionStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(AuthenticationData, AuthenticationType)> { + let payload = parse_jwt_payload::(request_headers, state).await?; + if payload.check_in_blacklist(state).await? { + return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); + } + authorization::check_tenant( + payload.tenant_id.clone(), + &state.session_state().tenant.tenant_id, + )?; + let role_info = authorization::get_role_info(state, &payload).await?; + authorization::check_permission(self.required_permission, &role_info)?; + + let merchant_id_from_header = HeaderMapStruct::new(request_headers) + .get_id_type_from_header::(headers::X_MERCHANT_ID)?; + + // Check if token has access to MerchantId that has been requested through headers + if payload.merchant_id != merchant_id_from_header { + return Err(report!(errors::ApiErrorResponse::InvalidJwtToken)); + } + + let key_manager_state = &(&state.session_state()).into(); + + let key_store = state + .store() + .get_merchant_key_store_by_merchant_id( + key_manager_state, + &payload.merchant_id, + &state.store().get_master_key().to_vec().into(), + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken) + .attach_printable("Failed to fetch merchant key store for the merchant id")?; + + let merchant = state + .store() + .find_merchant_account_by_merchant_id( + key_manager_state, + &payload.merchant_id, + &key_store, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken) + .attach_printable("Failed to fetch merchant account for the merchant id")?; + + let auth = AuthenticationData { + merchant_account: merchant, + platform_merchant_account: None, + key_store, + profile_id: Some(payload.profile_id), + }; + + Ok(( + auth, + AuthenticationType::MerchantJwt { + merchant_id: payload.merchant_id, + user_id: Some(payload.user_id), + }, + )) + } +} + +#[cfg(feature = "v2")] +#[async_trait] +impl AuthenticateAndFetch for JWTAuthMerchantFromHeader +where + A: SessionStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(AuthenticationData, AuthenticationType)> { + let payload = parse_jwt_payload::(request_headers, state).await?; + if payload.check_in_blacklist(state).await? { + return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); + } + let profile_id = + get_id_type_by_key_from_headers(headers::X_PROFILE_ID.to_string(), request_headers)? + .get_required_value(headers::X_PROFILE_ID)?; + + let role_info = authorization::get_role_info(state, &payload).await?; + authorization::check_permission(self.required_permission, &role_info)?; + + let merchant_id_from_header = HeaderMapStruct::new(request_headers) + .get_id_type_from_header::(headers::X_MERCHANT_ID)?; + + // Check if token has access to MerchantId that has been requested through headers + if payload.merchant_id != merchant_id_from_header { + return Err(report!(errors::ApiErrorResponse::InvalidJwtToken)); + } + + let key_manager_state = &(&state.session_state()).into(); + + let key_store = state + .store() + .get_merchant_key_store_by_merchant_id( + key_manager_state, + &payload.merchant_id, + &state.store().get_master_key().to_vec().into(), + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken) + .attach_printable("Failed to fetch merchant key store for the merchant id")?; + + let profile = state + .store() + .find_business_profile_by_merchant_id_profile_id( + key_manager_state, + &key_store, + &payload.merchant_id, + &profile_id, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + let merchant = state + .store() + .find_merchant_account_by_merchant_id( + key_manager_state, + &payload.merchant_id, + &key_store, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken) + .attach_printable("Failed to fetch merchant account for the merchant id")?; + + let auth = AuthenticationData { + merchant_account: merchant, + key_store, + profile, + platform_merchant_account: None, + }; + + Ok(( + auth, + AuthenticationType::MerchantJwt { + merchant_id: payload.merchant_id, + user_id: Some(payload.user_id), + }, + )) + } } +#[cfg(feature = "v2")] #[async_trait] -impl AuthenticateAndFetch<(), A> for JWTAuth +impl AuthenticateAndFetch for JWTAuthMerchantFromHeader where A: SessionStateInfo + Sync, { @@ -1027,18 +2260,54 @@ where &self, request_headers: &HeaderMap, state: &A, - ) -> RouterResult<((), AuthenticationType)> { + ) -> RouterResult<(AuthenticationDataWithoutProfile, AuthenticationType)> { let payload = parse_jwt_payload::(request_headers, state).await?; if payload.check_in_blacklist(state).await? { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } let role_info = authorization::get_role_info(state, &payload).await?; - authorization::check_permission(&self.permission, &role_info)?; - authorization::check_entity(self.minimum_entity_level, &role_info)?; + authorization::check_permission(self.required_permission, &role_info)?; + + let merchant_id_from_header = HeaderMapStruct::new(request_headers) + .get_id_type_from_header::(headers::X_MERCHANT_ID)?; + + // Check if token has access to MerchantId that has been requested through headers + if payload.merchant_id != merchant_id_from_header { + return Err(report!(errors::ApiErrorResponse::InvalidJwtToken)); + } + + let key_manager_state = &(&state.session_state()).into(); + + let key_store = state + .store() + .get_merchant_key_store_by_merchant_id( + key_manager_state, + &payload.merchant_id, + &state.store().get_master_key().to_vec().into(), + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken) + .attach_printable("Failed to fetch merchant key store for the merchant id")?; + + let merchant = state + .store() + .find_merchant_account_by_merchant_id( + key_manager_state, + &payload.merchant_id, + &key_store, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken) + .attach_printable("Failed to fetch merchant account for the merchant id")?; + + let auth = AuthenticationDataWithoutProfile { + merchant_account: merchant, + key_store, + }; Ok(( - (), + auth, AuthenticationType::MerchantJwt { merchant_id: payload.merchant_id, user_id: Some(payload.user_id), @@ -1047,9 +2316,8 @@ where } } -#[cfg(feature = "olap")] #[async_trait] -impl AuthenticateAndFetch for JWTAuth +impl AuthenticateAndFetch<(), A> for JWTAuthMerchantFromRoute where A: SessionStateInfo + Sync, { @@ -1057,24 +2325,25 @@ where &self, request_headers: &HeaderMap, state: &A, - ) -> RouterResult<(UserFromToken, AuthenticationType)> { + ) -> RouterResult<((), AuthenticationType)> { let payload = parse_jwt_payload::(request_headers, state).await?; if payload.check_in_blacklist(state).await? { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } + authorization::check_tenant( + payload.tenant_id.clone(), + &state.session_state().tenant.tenant_id, + )?; let role_info = authorization::get_role_info(state, &payload).await?; - authorization::check_permission(&self.permission, &role_info)?; - authorization::check_entity(self.minimum_entity_level, &role_info)?; + authorization::check_permission(self.required_permission, &role_info)?; + // Check if token has access to MerchantId that has been requested through query param + if payload.merchant_id != self.merchant_id { + return Err(report!(errors::ApiErrorResponse::InvalidJwtToken)); + } Ok(( - UserFromToken { - user_id: payload.user_id.clone(), - merchant_id: payload.merchant_id.clone(), - org_id: payload.org_id, - role_id: payload.role_id, - profile_id: payload.profile_id, - }, + (), AuthenticationType::MerchantJwt { merchant_id: payload.merchant_id, user_id: Some(payload.user_id), @@ -1083,9 +2352,9 @@ where } } -#[cfg(feature = "olap")] +#[cfg(feature = "v1")] #[async_trait] -impl AuthenticateAndFetch for JWTAuth +impl AuthenticateAndFetch for JWTAuthMerchantFromRoute where A: SessionStateInfo + Sync, { @@ -1093,15 +2362,22 @@ where &self, request_headers: &HeaderMap, state: &A, - ) -> RouterResult<(AuthenticationDataWithMultipleProfiles, AuthenticationType)> { + ) -> RouterResult<(AuthenticationData, AuthenticationType)> { let payload = parse_jwt_payload::(request_headers, state).await?; if payload.check_in_blacklist(state).await? { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } + authorization::check_tenant( + payload.tenant_id.clone(), + &state.session_state().tenant.tenant_id, + )?; + + if payload.merchant_id != self.merchant_id { + return Err(report!(errors::ApiErrorResponse::InvalidJwtToken)); + } let role_info = authorization::get_role_info(state, &payload).await?; - authorization::check_permission(&self.permission, &role_info)?; - authorization::check_entity(self.minimum_entity_level, &role_info)?; + authorization::check_permission(self.required_permission, &role_info)?; let key_manager_state = &(&state.session_state()).into(); let key_store = state @@ -1112,7 +2388,7 @@ where &state.store().get_master_key().to_vec().into(), ) .await - .change_context(errors::ApiErrorResponse::InvalidJwtToken) + .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken) .attach_printable("Failed to fetch merchant key store for the merchant id")?; let merchant = state @@ -1123,30 +2399,28 @@ where &key_store, ) .await - .change_context(errors::ApiErrorResponse::InvalidJwtToken)?; + .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken) + .attach_printable("Failed to fetch merchant account for the merchant id")?; + let auth = AuthenticationData { + merchant_account: merchant, + platform_merchant_account: None, + key_store, + profile_id: Some(payload.profile_id), + }; Ok(( - AuthenticationDataWithMultipleProfiles { - key_store, - merchant_account: merchant, - profile_id_list: None, - }, + auth.clone(), AuthenticationType::MerchantJwt { - merchant_id: payload.merchant_id, + merchant_id: auth.merchant_account.get_id().clone(), user_id: Some(payload.user_id), }, )) } } -pub struct JWTAuthOrganizationFromRoute { - pub organization_id: id_type::OrganizationId, - pub required_permission: Permission, - pub minimum_entity_level: EntityType, -} - +#[cfg(feature = "v2")] #[async_trait] -impl AuthenticateAndFetch<(), A> for JWTAuthOrganizationFromRoute +impl AuthenticateAndFetch for JWTAuthMerchantFromRoute where A: SessionStateInfo + Sync, { @@ -1154,79 +2428,73 @@ where &self, request_headers: &HeaderMap, state: &A, - ) -> RouterResult<((), AuthenticationType)> { + ) -> RouterResult<(AuthenticationData, AuthenticationType)> { let payload = parse_jwt_payload::(request_headers, state).await?; + let profile_id = + get_id_type_by_key_from_headers(headers::X_PROFILE_ID.to_string(), request_headers)? + .get_required_value(headers::X_PROFILE_ID)?; if payload.check_in_blacklist(state).await? { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } - let role_info = authorization::get_role_info(state, &payload).await?; - authorization::check_permission(&self.required_permission, &role_info)?; - authorization::check_entity(self.minimum_entity_level, &role_info)?; - - // Check if token has access to Organization that has been requested in the route - if payload.org_id != self.organization_id { + if payload.merchant_id != self.merchant_id { return Err(report!(errors::ApiErrorResponse::InvalidJwtToken)); } - Ok(( - (), - AuthenticationType::OrganizationJwt { - org_id: payload.org_id, - user_id: payload.user_id, - }, - )) - } -} - -pub struct JWTAuthMerchantFromRoute { - pub merchant_id: id_type::MerchantId, - pub required_permission: Permission, - pub minimum_entity_level: EntityType, -} - -pub struct JWTAuthMerchantFromHeader { - pub required_permission: Permission, - pub minimum_entity_level: EntityType, -} - -#[async_trait] -impl AuthenticateAndFetch<(), A> for JWTAuthMerchantFromHeader -where - A: SessionStateInfo + Sync, -{ - async fn authenticate_and_fetch( - &self, - request_headers: &HeaderMap, - state: &A, - ) -> RouterResult<((), AuthenticationType)> { - let payload = parse_jwt_payload::(request_headers, state).await?; - if payload.check_in_blacklist(state).await? { - return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); - } let role_info = authorization::get_role_info(state, &payload).await?; - authorization::check_permission(&self.required_permission, &role_info)?; - authorization::check_entity(self.minimum_entity_level, &role_info)?; + authorization::check_permission(self.required_permission, &role_info)?; - let merchant_id_from_header = - HeaderMapStruct::new(request_headers).get_merchant_id_from_header()?; + let key_manager_state = &(&state.session_state()).into(); + let key_store = state + .store() + .get_merchant_key_store_by_merchant_id( + key_manager_state, + &payload.merchant_id, + &state.store().get_master_key().to_vec().into(), + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken) + .attach_printable("Failed to fetch merchant key store for the merchant id")?; + let profile = state + .store() + .find_business_profile_by_merchant_id_profile_id( + key_manager_state, + &key_store, + &payload.merchant_id, + &profile_id, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + let merchant = state + .store() + .find_merchant_account_by_merchant_id( + key_manager_state, + &payload.merchant_id, + &key_store, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken) + .attach_printable("Failed to fetch merchant account for the merchant id")?; - // Check if token has access to MerchantId that has been requested through headers - if payload.merchant_id != merchant_id_from_header { - return Err(report!(errors::ApiErrorResponse::InvalidJwtToken)); - } + let auth = AuthenticationData { + merchant_account: merchant, + key_store, + profile, + platform_merchant_account: None, + }; Ok(( - (), + auth.clone(), AuthenticationType::MerchantJwt { - merchant_id: payload.merchant_id, + merchant_id: auth.merchant_account.get_id().clone(), user_id: Some(payload.user_id), }, )) } } +#[cfg(feature = "v2")] #[async_trait] -impl AuthenticateAndFetch for JWTAuthMerchantFromHeader +impl AuthenticateAndFetch for JWTAuthMerchantFromRoute where A: SessionStateInfo + Sync, { @@ -1234,26 +2502,21 @@ where &self, request_headers: &HeaderMap, state: &A, - ) -> RouterResult<(AuthenticationData, AuthenticationType)> { + ) -> RouterResult<(AuthenticationDataWithoutProfile, AuthenticationType)> { let payload = parse_jwt_payload::(request_headers, state).await?; + if payload.check_in_blacklist(state).await? { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } - let role_info = authorization::get_role_info(state, &payload).await?; - authorization::check_permission(&self.required_permission, &role_info)?; - authorization::check_entity(self.minimum_entity_level, &role_info)?; - - let merchant_id_from_header = - HeaderMapStruct::new(request_headers).get_merchant_id_from_header()?; - - // Check if token has access to MerchantId that has been requested through headers - if payload.merchant_id != merchant_id_from_header { + if payload.merchant_id != self.merchant_id { return Err(report!(errors::ApiErrorResponse::InvalidJwtToken)); } - let key_manager_state = &(&state.session_state()).into(); + let role_info = authorization::get_role_info(state, &payload).await?; + authorization::check_permission(self.required_permission, &role_info)?; + let key_manager_state = &(&state.session_state()).into(); let key_store = state .store() .get_merchant_key_store_by_merchant_id( @@ -1276,24 +2539,29 @@ where .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken) .attach_printable("Failed to fetch merchant account for the merchant id")?; - let auth = AuthenticationData { + let auth = AuthenticationDataWithoutProfile { merchant_account: merchant, key_store, - profile_id: payload.profile_id, }; - Ok(( - auth, + auth.clone(), AuthenticationType::MerchantJwt { - merchant_id: payload.merchant_id, + merchant_id: auth.merchant_account.get_id().clone(), user_id: Some(payload.user_id), }, )) } } +pub struct JWTAuthMerchantAndProfileFromRoute { + pub merchant_id: id_type::MerchantId, + pub profile_id: id_type::ProfileId, + pub required_permission: Permission, +} + +#[cfg(feature = "v1")] #[async_trait] -impl AuthenticateAndFetch<(), A> for JWTAuthMerchantFromRoute +impl AuthenticateAndFetch for JWTAuthMerchantAndProfileFromRoute where A: SessionStateInfo + Sync, { @@ -1301,32 +2569,75 @@ where &self, request_headers: &HeaderMap, state: &A, - ) -> RouterResult<((), AuthenticationType)> { + ) -> RouterResult<(AuthenticationData, AuthenticationType)> { let payload = parse_jwt_payload::(request_headers, state).await?; if payload.check_in_blacklist(state).await? { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } + authorization::check_tenant( + payload.tenant_id.clone(), + &state.session_state().tenant.tenant_id, + )?; - let role_info = authorization::get_role_info(state, &payload).await?; - authorization::check_permission(&self.required_permission, &role_info)?; - authorization::check_entity(self.minimum_entity_level, &role_info)?; - - // Check if token has access to MerchantId that has been requested through query param if payload.merchant_id != self.merchant_id { return Err(report!(errors::ApiErrorResponse::InvalidJwtToken)); } + + if payload.profile_id != self.profile_id { + return Err(report!(errors::ApiErrorResponse::InvalidJwtToken)); + } + + let role_info = authorization::get_role_info(state, &payload).await?; + authorization::check_permission(self.required_permission, &role_info)?; + + let key_manager_state = &(&state.session_state()).into(); + let key_store = state + .store() + .get_merchant_key_store_by_merchant_id( + key_manager_state, + &payload.merchant_id, + &state.store().get_master_key().to_vec().into(), + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken) + .attach_printable("Failed to fetch merchant key store for the merchant id")?; + + let merchant = state + .store() + .find_merchant_account_by_merchant_id( + key_manager_state, + &payload.merchant_id, + &key_store, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken) + .attach_printable("Failed to fetch merchant account for the merchant id")?; + + let auth = AuthenticationData { + merchant_account: merchant, + platform_merchant_account: None, + key_store, + profile_id: Some(payload.profile_id), + }; Ok(( - (), - AuthenticationType::MerchantJwt { - merchant_id: payload.merchant_id, - user_id: Some(payload.user_id), + auth.clone(), + AuthenticationType::MerchantJwtWithProfileId { + merchant_id: auth.merchant_account.get_id().clone(), + profile_id: auth.profile_id.clone(), + user_id: payload.user_id, }, )) } } +pub struct JWTAuthProfileFromRoute { + pub profile_id: id_type::ProfileId, + pub required_permission: Permission, +} + +#[cfg(feature = "v1")] #[async_trait] -impl AuthenticateAndFetch for JWTAuthMerchantFromRoute +impl AuthenticateAndFetch for JWTAuthProfileFromRoute where A: SessionStateInfo + Sync, { @@ -1335,18 +2646,17 @@ where request_headers: &HeaderMap, state: &A, ) -> RouterResult<(AuthenticationData, AuthenticationType)> { - let payload = parse_jwt_payload::(request_headers, state).await?; - if payload.check_in_blacklist(state).await? { - return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); - } - - if payload.merchant_id != self.merchant_id { - return Err(report!(errors::ApiErrorResponse::InvalidJwtToken)); + let payload = parse_jwt_payload::(request_headers, state).await?; + if payload.check_in_blacklist(state).await? { + return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } + authorization::check_tenant( + payload.tenant_id.clone(), + &state.session_state().tenant.tenant_id, + )?; let role_info = authorization::get_role_info(state, &payload).await?; - authorization::check_permission(&self.required_permission, &role_info)?; - authorization::check_entity(self.minimum_entity_level, &role_info)?; + authorization::check_permission(self.required_permission, &role_info)?; let key_manager_state = &(&state.session_state()).into(); let key_store = state @@ -1371,29 +2681,30 @@ where .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken) .attach_printable("Failed to fetch merchant account for the merchant id")?; - let auth = AuthenticationData { - merchant_account: merchant, - key_store, - profile_id: payload.profile_id, - }; - Ok(( - auth.clone(), - AuthenticationType::MerchantJwt { - merchant_id: auth.merchant_account.get_id().clone(), - user_id: Some(payload.user_id), - }, - )) + if payload.profile_id != self.profile_id { + return Err(report!(errors::ApiErrorResponse::InvalidJwtToken)); + } else { + // if both of them are same then proceed with the profile id present in the request + let auth = AuthenticationData { + merchant_account: merchant, + platform_merchant_account: None, + key_store, + profile_id: Some(self.profile_id.clone()), + }; + Ok(( + auth.clone(), + AuthenticationType::MerchantJwt { + merchant_id: auth.merchant_account.get_id().clone(), + user_id: Some(payload.user_id), + }, + )) + } } } -pub struct JWTAuthMerchantAndProfileFromRoute { - pub merchant_id: id_type::MerchantId, - pub profile_id: id_type::ProfileId, - pub required_permission: Permission, - pub minimum_entity_level: EntityType, -} +#[cfg(feature = "v2")] #[async_trait] -impl AuthenticateAndFetch for JWTAuthMerchantAndProfileFromRoute +impl AuthenticateAndFetch for JWTAuthProfileFromRoute where A: SessionStateInfo + Sync, { @@ -1406,22 +2717,12 @@ where if payload.check_in_blacklist(state).await? { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } - - if payload.merchant_id != self.merchant_id { - return Err(report!(errors::ApiErrorResponse::InvalidJwtToken)); - } - - if payload - .profile_id - .as_ref() - .is_some_and(|profile_id| *profile_id != self.profile_id) - { - return Err(report!(errors::ApiErrorResponse::InvalidJwtToken)); - } + let profile_id = + get_id_type_by_key_from_headers(headers::X_PROFILE_ID.to_string(), request_headers)? + .get_required_value(headers::X_PROFILE_ID)?; let role_info = authorization::get_role_info(state, &payload).await?; - authorization::check_permission(&self.required_permission, &role_info)?; - authorization::check_entity(self.minimum_entity_level, &role_info)?; + authorization::check_permission(self.required_permission, &role_info)?; let key_manager_state = &(&state.session_state()).into(); let key_store = state @@ -1435,6 +2736,16 @@ where .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken) .attach_printable("Failed to fetch merchant key store for the merchant id")?; + let profile = state + .store() + .find_business_profile_by_merchant_id_profile_id( + key_manager_state, + &key_store, + &payload.merchant_id, + &profile_id, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; let merchant = state .store() .find_merchant_account_by_merchant_id( @@ -1449,27 +2760,51 @@ where let auth = AuthenticationData { merchant_account: merchant, key_store, - profile_id: payload.profile_id, + profile, + platform_merchant_account: None, }; Ok(( auth.clone(), - AuthenticationType::MerchantJwtWithProfileId { + AuthenticationType::MerchantJwt { merchant_id: auth.merchant_account.get_id().clone(), - profile_id: auth.profile_id.clone(), - user_id: payload.user_id, + user_id: Some(payload.user_id), }, )) } } -pub struct JWTAuthProfileFromRoute { - pub profile_id: id_type::ProfileId, - pub required_permission: Permission, - pub minimum_entity_level: EntityType, +pub async fn parse_jwt_payload(headers: &HeaderMap, state: &A) -> RouterResult +where + T: serde::de::DeserializeOwned, + A: SessionStateInfo + Sync, +{ + let cookie_token_result = + get_cookie_from_header(headers).and_then(cookies::get_jwt_from_cookies); + let auth_header_token_result = get_jwt_from_authorization_header(headers); + let force_cookie = state.conf().user.force_cookies; + + logger::info!( + user_agent = ?headers.get(headers::USER_AGENT), + header_names = ?headers.keys().collect::>(), + is_token_equal = + auth_header_token_result.as_deref().ok() == cookie_token_result.as_deref().ok(), + cookie_error = ?cookie_token_result.as_ref().err(), + token_error = ?auth_header_token_result.as_ref().err(), + force_cookie, + ); + + let final_token = if force_cookie { + cookie_token_result? + } else { + auth_header_token_result?.to_owned() + }; + + decode_jwt(&final_token, state).await } +#[cfg(feature = "v1")] #[async_trait] -impl AuthenticateAndFetch for JWTAuthProfileFromRoute +impl AuthenticateAndFetch for JWTAuth where A: SessionStateInfo + Sync, { @@ -1482,10 +2817,13 @@ where if payload.check_in_blacklist(state).await? { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } + authorization::check_tenant( + payload.tenant_id.clone(), + &state.session_state().tenant.tenant_id, + )?; let role_info = authorization::get_role_info(state, &payload).await?; - authorization::check_permission(&self.required_permission, &role_info)?; - authorization::check_entity(self.minimum_entity_level, &role_info)?; + authorization::check_permission(self.permission, &role_info)?; let key_manager_state = &(&state.session_state()).into(); let key_store = state @@ -1509,61 +2847,24 @@ where .await .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken) .attach_printable("Failed to fetch merchant account for the merchant id")?; - - if let Some(ref payload_profile_id) = payload.profile_id { - if *payload_profile_id != self.profile_id { - return Err(report!(errors::ApiErrorResponse::InvalidJwtToken)); - } else { - // if both of them are same then proceed with the profile id present in the request - let auth = AuthenticationData { - merchant_account: merchant, - key_store, - profile_id: Some(self.profile_id.clone()), - }; - Ok(( - auth.clone(), - AuthenticationType::MerchantJwt { - merchant_id: auth.merchant_account.get_id().clone(), - user_id: Some(payload.user_id), - }, - )) - } - } else { - // if profile_id is not present in the auth_layer itself then no change in behaviour - let auth = AuthenticationData { - merchant_account: merchant, - key_store, - profile_id: payload.profile_id, - }; - Ok(( - auth.clone(), - AuthenticationType::MerchantJwt { - merchant_id: auth.merchant_account.get_id().clone(), - user_id: Some(payload.user_id), - }, - )) - } + let merchant_id = merchant.get_id().clone(); + let auth = AuthenticationData { + merchant_account: merchant, + platform_merchant_account: None, + key_store, + profile_id: Some(payload.profile_id), + }; + Ok(( + auth, + AuthenticationType::MerchantJwt { + merchant_id, + user_id: Some(payload.user_id), + }, + )) } } -pub async fn parse_jwt_payload(headers: &HeaderMap, state: &A) -> RouterResult -where - T: serde::de::DeserializeOwned, - A: SessionStateInfo + Sync, -{ - let token = match get_cookie_from_header(headers).and_then(cookies::parse_cookie) { - Ok(cookies) => cookies, - Err(error) => { - let token = get_jwt_from_authorization_header(headers); - if token.is_err() { - logger::error!(?error); - } - token?.to_owned() - } - }; - decode_jwt(&token, state).await -} - +#[cfg(feature = "v2")] #[async_trait] impl AuthenticateAndFetch for JWTAuth where @@ -1578,10 +2879,16 @@ where if payload.check_in_blacklist(state).await? { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } + authorization::check_tenant( + payload.tenant_id.clone(), + &state.session_state().tenant.tenant_id, + )?; + + let profile_id = HeaderMapStruct::new(request_headers) + .get_id_type_from_header::(headers::X_PROFILE_ID)?; let role_info = authorization::get_role_info(state, &payload).await?; - authorization::check_permission(&self.permission, &role_info)?; - authorization::check_entity(self.minimum_entity_level, &role_info)?; + authorization::check_permission(self.permission, &role_info)?; let key_manager_state = &(&state.session_state()).into(); let key_store = state @@ -1595,6 +2902,16 @@ where .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken) .attach_printable("Failed to fetch merchant key store for the merchant id")?; + let profile = state + .store() + .find_business_profile_by_merchant_id_profile_id( + key_manager_state, + &key_store, + &payload.merchant_id, + &profile_id, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; let merchant = state .store() .find_merchant_account_by_merchant_id( @@ -1605,16 +2922,17 @@ where .await .to_not_found_response(errors::ApiErrorResponse::InvalidJwtToken) .attach_printable("Failed to fetch merchant account for the merchant id")?; - + let merchant_id = merchant.get_id().clone(); let auth = AuthenticationData { merchant_account: merchant, key_store, - profile_id: payload.profile_id, + profile, + platform_merchant_account: None, }; Ok(( - auth.clone(), + auth, AuthenticationType::MerchantJwt { - merchant_id: auth.merchant_account.get_id().clone(), + merchant_id, user_id: Some(payload.user_id), }, )) @@ -1623,6 +2941,7 @@ where pub type AuthenticationDataWithUserId = (AuthenticationData, String); +#[cfg(feature = "v1")] #[async_trait] impl AuthenticateAndFetch for JWTAuth where @@ -1637,10 +2956,13 @@ where if payload.check_in_blacklist(state).await? { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } + authorization::check_tenant( + payload.tenant_id.clone(), + &state.session_state().tenant.tenant_id, + )?; let role_info = authorization::get_role_info(state, &payload).await?; - authorization::check_permission(&self.permission, &role_info)?; - authorization::check_entity(self.minimum_entity_level, &role_info)?; + authorization::check_permission(self.permission, &role_info)?; let key_manager_state = &(&state.session_state()).into(); let key_store = state @@ -1666,8 +2988,9 @@ where let auth = AuthenticationData { merchant_account: merchant, + platform_merchant_account: None, key_store, - profile_id: payload.profile_id, + profile_id: Some(payload.profile_id), }; Ok(( (auth.clone(), payload.user_id.clone()), @@ -1697,6 +3020,11 @@ where return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } + authorization::check_tenant( + payload.tenant_id.clone(), + &state.session_state().tenant.tenant_id, + )?; + Ok(( UserFromToken { user_id: payload.user_id.clone(), @@ -1704,6 +3032,7 @@ where org_id: payload.org_id, role_id: payload.role_id, profile_id: payload.profile_id, + tenant_id: payload.tenant_id, }, AuthenticationType::MerchantJwt { merchant_id: payload.merchant_id, @@ -1728,11 +3057,16 @@ where if payload.check_in_blacklist(state).await? { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } + authorization::check_tenant( + payload.tenant_id.clone(), + &state.session_state().tenant.tenant_id, + )?; Ok(((), AuthenticationType::NoAuth)) } } +#[cfg(feature = "v1")] #[async_trait] impl AuthenticateAndFetch for DashboardNoPermissionAuth where @@ -1744,6 +3078,11 @@ where state: &A, ) -> RouterResult<(AuthenticationData, AuthenticationType)> { let payload = parse_jwt_payload::(request_headers, state).await?; + authorization::check_tenant( + payload.tenant_id.clone(), + &state.session_state().tenant.tenant_id, + )?; + let key_manager_state = &(&state.session_state()).into(); let key_store = state .store() @@ -1768,8 +3107,9 @@ where let auth = AuthenticationData { merchant_account: merchant, + platform_merchant_account: None, key_store, - profile_id: payload.profile_id, + profile_id: Some(payload.profile_id), }; Ok(( auth.clone(), @@ -1791,42 +3131,43 @@ impl ClientSecretFetch for payouts::PayoutCreateRequest { } } +#[cfg(feature = "v1")] impl ClientSecretFetch for payments::PaymentsRequest { fn get_client_secret(&self) -> Option<&String> { self.client_secret.as_ref() } } -impl ClientSecretFetch for PaymentMethodListRequest { +#[cfg(feature = "v1")] +impl ClientSecretFetch for payments::PaymentsRetrieveRequest { fn get_client_secret(&self) -> Option<&String> { self.client_secret.as_ref() } } -#[cfg(all( - any(feature = "v2", feature = "v1"), - not(feature = "payment_methods_v2") -))] -impl ClientSecretFetch for PaymentMethodCreate { +impl ClientSecretFetch for PaymentMethodListRequest { fn get_client_secret(&self) -> Option<&String> { self.client_secret.as_ref() } } -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -impl ClientSecretFetch for PaymentMethodIntentConfirm { +impl ClientSecretFetch for payments::PaymentsPostSessionTokensRequest { fn get_client_secret(&self) -> Option<&String> { - Some(&self.client_secret) + Some(self.client_secret.peek()) } } -impl ClientSecretFetch for api_models::cards_info::CardsInfoRequest { +#[cfg(all( + any(feature = "v2", feature = "v1"), + not(feature = "payment_methods_v2") +))] +impl ClientSecretFetch for PaymentMethodCreate { fn get_client_secret(&self) -> Option<&String> { self.client_secret.as_ref() } } -impl ClientSecretFetch for payments::PaymentsRetrieveRequest { +impl ClientSecretFetch for api_models::cards_info::CardsInfoRequest { fn get_client_secret(&self) -> Option<&String> { self.client_secret.as_ref() } @@ -1850,6 +3191,7 @@ impl ClientSecretFetch for api_models::pm_auth::ExchangeTokenCreateRequest { } } +#[cfg(feature = "v1")] impl ClientSecretFetch for api_models::payment_methods::PaymentMethodUpdate { fn get_client_secret(&self) -> Option<&String> { self.client_secret.as_ref() @@ -1940,6 +3282,20 @@ where } } +pub fn is_ephemeral_or_publishible_auth( + headers: &HeaderMap, +) -> RouterResult>> { + let api_key = get_api_key(headers)?; + + if api_key.starts_with("epk") { + Ok(Box::new(EphemeralKeyAuth)) + } else if api_key.starts_with("pk_") { + Ok(Box::new(HeaderAuth(PublishableKeyAuth))) + } else { + Ok(Box::new(HeaderAuth(ApiKeyAuth))) + } +} + pub fn is_ephemeral_auth( headers: &HeaderMap, ) -> RouterResult>> { @@ -1955,7 +3311,7 @@ pub fn is_ephemeral_auth( pub fn is_jwt_auth(headers: &HeaderMap) -> bool { headers.get(headers::AUTHORIZATION).is_some() || get_cookie_from_header(headers) - .and_then(cookies::parse_cookie) + .and_then(cookies::get_jwt_from_cookies) .is_ok() } @@ -1990,6 +3346,20 @@ pub fn get_header_value_by_key(key: String, headers: &HeaderMap) -> RouterResult }) .transpose() } +pub fn get_id_type_by_key_from_headers( + key: String, + headers: &HeaderMap, +) -> RouterResult> { + get_header_value_by_key(key.clone(), headers)? + .map(|str_value| T::from_str(str_value)) + .transpose() + .map_err(|_err| { + error_stack::report!(errors::ApiErrorResponse::InvalidDataFormat { + field_name: key, + expected_format: "Valid Id String".to_string(), + }) + }) +} pub fn get_jwt_from_authorization_header(headers: &HeaderMap) -> RouterResult<&str> { headers @@ -2003,10 +3373,13 @@ pub fn get_jwt_from_authorization_header(headers: &HeaderMap) -> RouterResult<&s } pub fn get_cookie_from_header(headers: &HeaderMap) -> RouterResult<&str> { - headers + let cookie = headers .get(cookies::get_cookie_header()) - .and_then(|header_value| header_value.to_str().ok()) - .ok_or(errors::ApiErrorResponse::InvalidCookie.into()) + .ok_or(report!(errors::ApiErrorResponse::CookieNotFound))?; + + cookie + .to_str() + .change_context(errors::ApiErrorResponse::InvalidCookie) } pub fn strip_jwt_token(token: &str) -> RouterResult<&str> { @@ -2043,9 +3416,12 @@ where if payload.check_in_blacklist(state).await? { return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); } + authorization::check_tenant( + payload.tenant_id.clone(), + &state.session_state().tenant.tenant_id, + )?; let role_info = authorization::get_role_info(state, &payload).await?; - authorization::check_permission(&self.permission, &role_info)?; - authorization::check_entity(self.minimum_entity_level, &role_info)?; + authorization::check_permission(self.permission, &role_info)?; let key_manager_state = &(&state.session_state()).into(); let key_store = state @@ -2095,3 +3471,181 @@ where Ok((auth, auth_type)) } } + +async fn get_connected_merchant_account( + state: &A, + connected_merchant_id: id_type::MerchantId, + platform_org_id: id_type::OrganizationId, +) -> RouterResult +where + A: SessionStateInfo + Sync, +{ + let key_manager_state = &(&state.session_state()).into(); + let key_store = state + .store() + .get_merchant_key_store_by_merchant_id( + key_manager_state, + &connected_merchant_id, + &state.store().get_master_key().to_vec().into(), + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InvalidPlatformOperation) + .attach_printable("Failed to fetch merchant key store for the merchant id")?; + + let connected_merchant_account = state + .store() + .find_merchant_account_by_merchant_id(key_manager_state, &connected_merchant_id, &key_store) + .await + .to_not_found_response(errors::ApiErrorResponse::InvalidPlatformOperation) + .attach_printable("Failed to fetch merchant account for the merchant id")?; + + if platform_org_id != connected_merchant_account.organization_id { + return Err(errors::ApiErrorResponse::InvalidPlatformOperation) + .attach_printable("Access for merchant id Unauthorized"); + } + + Ok(connected_merchant_account) +} + +async fn get_platform_merchant_account( + state: &A, + request_headers: &HeaderMap, + merchant_account: domain::MerchantAccount, +) -> RouterResult<(domain::MerchantAccount, Option)> +where + A: SessionStateInfo + Sync, +{ + let connected_merchant_id = + get_and_validate_connected_merchant_id(request_headers, &merchant_account)?; + + match connected_merchant_id { + Some(merchant_id) => { + let connected_merchant_account = get_connected_merchant_account( + state, + merchant_id, + merchant_account.organization_id.clone(), + ) + .await?; + Ok((connected_merchant_account, Some(merchant_account))) + } + None => Ok((merchant_account, None)), + } +} + +fn get_and_validate_connected_merchant_id( + request_headers: &HeaderMap, + merchant_account: &domain::MerchantAccount, +) -> RouterResult> { + HeaderMapStruct::new(request_headers) + .get_id_type_from_header_if_present::( + headers::X_CONNECTED_MERCHANT_ID, + )? + .map(|merchant_id| { + merchant_account + .is_platform_account + .then_some(merchant_id) + .ok_or(errors::ApiErrorResponse::InvalidPlatformOperation) + }) + .transpose() + .attach_printable("Non platform_merchant_account using X_CONNECTED_MERCHANT_ID header") +} + +fn throw_error_if_platform_merchant_authentication_required( + request_headers: &HeaderMap, +) -> RouterResult<()> { + HeaderMapStruct::new(request_headers) + .get_id_type_from_header_if_present::( + headers::X_CONNECTED_MERCHANT_ID, + )? + .map_or(Ok(()), |_| { + Err(errors::ApiErrorResponse::PlatformAccountAuthNotSupported.into()) + }) +} + +#[cfg(feature = "recon")] +#[async_trait] +impl AuthenticateAndFetch for JWTAuth +where + A: SessionStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(UserFromTokenWithRoleInfo, AuthenticationType)> { + let payload = parse_jwt_payload::(request_headers, state).await?; + if payload.check_in_blacklist(state).await? { + return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); + } + authorization::check_tenant( + payload.tenant_id.clone(), + &state.session_state().tenant.tenant_id, + )?; + let role_info = authorization::get_role_info(state, &payload).await?; + authorization::check_permission(self.permission, &role_info)?; + + let user = UserFromToken { + user_id: payload.user_id.clone(), + merchant_id: payload.merchant_id.clone(), + org_id: payload.org_id, + role_id: payload.role_id, + profile_id: payload.profile_id, + tenant_id: payload.tenant_id, + }; + + Ok(( + UserFromTokenWithRoleInfo { user, role_info }, + AuthenticationType::MerchantJwt { + merchant_id: payload.merchant_id, + user_id: Some(payload.user_id), + }, + )) + } +} + +#[cfg(feature = "recon")] +#[derive(serde::Serialize, serde::Deserialize)] +pub struct ReconToken { + pub user_id: String, + pub merchant_id: id_type::MerchantId, + pub role_id: String, + pub exp: u64, + pub org_id: id_type::OrganizationId, + pub profile_id: id_type::ProfileId, + pub tenant_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub acl: Option, +} + +#[cfg(all(feature = "olap", feature = "recon"))] +impl ReconToken { + pub async fn new_token( + user_id: String, + merchant_id: id_type::MerchantId, + settings: &Settings, + org_id: id_type::OrganizationId, + profile_id: id_type::ProfileId, + tenant_id: Option, + role_info: authorization::roles::RoleInfo, + ) -> UserResult { + let exp_duration = std::time::Duration::from_secs(consts::JWT_TOKEN_TIME_IN_SECS); + let exp = jwt::generate_exp(exp_duration)?.as_secs(); + let acl = role_info.get_recon_acl(); + let optional_acl_str = serde_json::to_string(&acl) + .inspect_err(|err| logger::error!("Failed to serialize acl to string: {}", err)) + .change_context(errors::UserErrors::InternalServerError) + .attach_printable("Failed to serialize acl to string. Using empty ACL") + .ok(); + let token_payload = Self { + user_id, + merchant_id, + role_id: role_info.get_role_id().to_string(), + exp, + org_id, + profile_id, + tenant_id, + acl: optional_acl_str, + }; + jwt::generate_jwt(&token_payload, settings).await + } +} diff --git a/crates/router/src/services/authentication/cookies.rs b/crates/router/src/services/authentication/cookies.rs index 965188408004..4a297c0f0eb9 100644 --- a/crates/router/src/services/authentication/cookies.rs +++ b/crates/router/src/services/authentication/cookies.rs @@ -49,7 +49,7 @@ pub fn remove_cookie_response() -> UserResponse<()> { Ok(ApplicationResponse::JsonWithHeaders(((), header))) } -pub fn parse_cookie(cookies: &str) -> RouterResult { +pub fn get_jwt_from_cookies(cookies: &str) -> RouterResult { Cookie::split_parse(cookies) .find_map(|cookie| { cookie @@ -57,8 +57,8 @@ pub fn parse_cookie(cookies: &str) -> RouterResult { .filter(|parsed_cookie| parsed_cookie.name() == JWT_TOKEN_COOKIE_NAME) .map(|parsed_cookie| parsed_cookie.value().to_owned()) }) - .ok_or(report!(ApiErrorResponse::InvalidCookie)) - .attach_printable("Cookie Parsing Failed") + .ok_or(report!(ApiErrorResponse::InvalidJwtToken)) + .attach_printable("Unable to find JWT token in cookies") } #[cfg(feature = "olap")] diff --git a/crates/router/src/services/authentication/decision.rs b/crates/router/src/services/authentication/decision.rs index d665b0caaf41..a1796f97c3b2 100644 --- a/crates/router/src/services/authentication/decision.rs +++ b/crates/router/src/services/authentication/decision.rs @@ -1,6 +1,5 @@ use common_utils::{errors::CustomResult, request::RequestContent}; use masking::{ErasedMaskSerialize, Secret}; -use router_env::opentelemetry::KeyValue; use serde::Serialize; use storage_impl::errors::ApiClientError; @@ -67,7 +66,7 @@ pub enum Identifiers { /// [`ApiKey`] is an authentication method that uses an API key. This is used with [`ApiKey`] ApiKey { merchant_id: common_utils::id_type::MerchantId, - key_id: String, + key_id: common_utils::id_type::ApiKeyId, }, /// [`PublishableKey`] is an authentication method that uses a publishable key. This is used with [`PublishableKey`] PublishableKey { merchant_id: String }, @@ -80,7 +79,7 @@ pub async fn add_api_key( state: &SessionState, api_key: Secret, merchant_id: common_utils::id_type::MerchantId, - key_id: String, + key_id: common_utils::id_type::ApiKeyId, expiry: Option, ) -> CustomResult<(), ApiClientError> { let decision_config = if let Some(config) = &state.conf.decision { @@ -179,10 +178,7 @@ pub async fn revoke_api_key( call_decision_service(state, decision_config, rule, RULE_DELETE_METHOD).await } -/// -/// /// Safety: i64::MAX < u64::MAX -/// #[allow(clippy::as_conversions)] pub fn convert_expiry(expiry: time::PrimitiveDateTime) -> u64 { let now = common_utils::date_time::now(); @@ -200,19 +196,13 @@ where E: std::fmt::Debug, F: futures::Future> + Send + 'static, { - metrics::API_KEY_REQUEST_INITIATED.add( - &metrics::CONTEXT, - 1, - &[KeyValue::new("type", request_type)], - ); + metrics::API_KEY_REQUEST_INITIATED + .add(1, router_env::metric_attributes!(("type", request_type))); tokio::spawn(async move { match future.await { Ok(_) => { - metrics::API_KEY_REQUEST_COMPLETED.add( - &metrics::CONTEXT, - 1, - &[KeyValue::new("type", request_type)], - ); + metrics::API_KEY_REQUEST_COMPLETED + .add(1, router_env::metric_attributes!(("type", request_type))); } Err(e) => { router_env::error!("Error in tracked job: {:?}", e); diff --git a/crates/router/src/services/authentication/detached.rs b/crates/router/src/services/authentication/detached.rs index af373d3e559d..9d77d2b1f072 100644 --- a/crates/router/src/services/authentication/detached.rs +++ b/crates/router/src/services/authentication/detached.rs @@ -1,7 +1,10 @@ use std::{borrow::Cow, string::ToString}; use actix_web::http::header::HeaderMap; -use common_utils::{crypto::VerifySignature, id_type::MerchantId}; +use common_utils::{ + crypto::VerifySignature, + id_type::{ApiKeyId, MerchantId}, +}; use error_stack::ResultExt; use hyperswitch_domain_models::errors::api_error_response::ApiErrorResponse; @@ -16,7 +19,7 @@ const HEADER_CHECKSUM: &str = "x-checksum"; pub struct ExtractedPayload { pub payload_type: PayloadType, pub merchant_id: Option, - pub key_id: Option, + pub key_id: Option, } #[derive(strum::EnumString, strum::Display, PartialEq, Debug)] @@ -61,13 +64,19 @@ impl ExtractedPayload { message: format!("`{}` header not present", HEADER_AUTH_TYPE), })?; + let key_id = headers + .get(HEADER_KEY_ID) + .and_then(|value| value.to_str().ok()) + .map(|key_id| ApiKeyId::try_from(Cow::from(key_id.to_string()))) + .transpose() + .change_context(ApiErrorResponse::InvalidRequestData { + message: format!("`{}` header is invalid or not present", HEADER_KEY_ID), + })?; + Ok(Self { payload_type: auth_type, merchant_id: Some(merchant_id), - key_id: headers - .get(HEADER_KEY_ID) - .and_then(|v| v.to_str().ok()) - .map(|v| v.to_string()), + key_id, }) } @@ -95,7 +104,7 @@ impl ExtractedPayload { &self .merchant_id .as_ref() - .map(|inner| append_option(inner.get_string_repr(), &self.key_id)), + .map(|inner| append_api_key(inner.get_string_repr(), &self.key_id)), ) } } @@ -107,3 +116,11 @@ fn append_option(prefix: &str, data: &Option) -> String { None => prefix.to_string(), } } + +#[inline] +fn append_api_key(prefix: &str, data: &Option) -> String { + match data { + Some(inner) => format!("{}:{}", prefix, inner.get_string_repr()), + None => prefix.to_string(), + } +} diff --git a/crates/router/src/services/authorization.rs b/crates/router/src/services/authorization.rs index 78af4c00884e..da296373d802 100644 --- a/crates/router/src/services/authorization.rs +++ b/crates/router/src/services/authorization.rs @@ -33,17 +33,18 @@ where return Ok(role_info.clone()); } - let role_info = - get_role_info_from_db(state, &token.role_id, &token.merchant_id, &token.org_id).await?; + let role_info = get_role_info_from_db(state, &token.role_id, &token.org_id).await?; let token_expiry = i64::try_from(token.exp).change_context(ApiErrorResponse::InternalServerError)?; let cache_ttl = token_expiry - common_utils::date_time::now_unix_timestamp(); - set_role_info_in_cache(state, &token.role_id, &role_info, cache_ttl) - .await - .map_err(|e| logger::error!("Failed to set role info in cache {e:?}")) - .ok(); + if cache_ttl > 0 { + set_role_info_in_cache(state, &token.role_id, &role_info, cache_ttl) + .await + .map_err(|e| logger::error!("Failed to set role info in cache {e:?}")) + .ok(); + } Ok(role_info) } @@ -66,15 +67,15 @@ pub fn get_cache_key_from_role_id(role_id: &str) -> String { async fn get_role_info_from_db( state: &A, role_id: &str, - merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, ) -> RouterResult where A: SessionStateInfo + Sync, { state - .store() - .find_role_by_role_id_in_merchant_scope(role_id, merchant_id, org_id) + .session_state() + .global_store + .find_by_role_id_and_org_id(role_id, org_id) .await .map(roles::RoleInfo::from) .to_not_found_response(ApiErrorResponse::InvalidJwtToken) @@ -98,7 +99,7 @@ where } pub fn check_permission( - required_permission: &permissions::Permission, + required_permission: permissions::Permission, role_info: &roles::RoleInfo, ) -> RouterResult<()> { role_info @@ -112,14 +113,18 @@ pub fn check_permission( ) } -pub fn check_entity( - required_minimum_entity: common_enums::EntityType, - role_info: &roles::RoleInfo, +pub fn check_tenant( + token_tenant_id: Option, + header_tenant_id: &id_type::TenantId, ) -> RouterResult<()> { - if required_minimum_entity > role_info.get_entity_type() { - Err(ApiErrorResponse::AccessForbidden { - resource: required_minimum_entity.to_string(), - })?; + if let Some(tenant_id) = token_tenant_id { + if tenant_id != *header_tenant_id { + return Err(ApiErrorResponse::InvalidJwtToken).attach_printable(format!( + "Token tenant ID: '{}' does not match Header tenant ID: '{}'", + tenant_id.get_string_repr().to_owned(), + header_tenant_id.get_string_repr().to_owned() + )); + } } Ok(()) } diff --git a/crates/router/src/services/authorization/info.rs b/crates/router/src/services/authorization/info.rs index 822035c7925d..e02838b3e000 100644 --- a/crates/router/src/services/authorization/info.rs +++ b/crates/router/src/services/authorization/info.rs @@ -1,203 +1,21 @@ -use api_models::user_role::{GroupInfo, ParentGroup, PermissionInfo}; -use common_enums::PermissionGroup; -use strum::{EnumIter, IntoEnumIterator}; - -use super::{permission_groups::get_permissions_vec, permissions::Permission}; - -pub fn get_module_authorization_info() -> Vec { - PermissionModule::iter() - .map(|module| ModuleInfo::new(&module)) - .collect() -} +use api_models::user_role::GroupInfo; +use common_enums::{ParentGroup, PermissionGroup}; +use strum::IntoEnumIterator; +// TODO: To be deprecated pub fn get_group_authorization_info() -> Vec { PermissionGroup::iter() .map(get_group_info_from_permission_group) .collect() } -pub fn get_permission_info_from_permissions(permissions: &[Permission]) -> Vec { - permissions - .iter() - .map(|&per| PermissionInfo { - description: Permission::get_permission_description(&per), - enum_name: per.into(), - }) - .collect() -} - -// TODO: Deprecate once groups are stable -#[derive(PartialEq, EnumIter, Clone)] -pub enum PermissionModule { - Payments, - Refunds, - MerchantAccount, - Connectors, - Routing, - Analytics, - Mandates, - Customer, - Disputes, - ThreeDsDecisionManager, - SurchargeDecisionManager, - AccountCreate, - Payouts, - Recon, -} - -impl PermissionModule { - pub fn get_module_description(&self) -> &'static str { - match self { - Self::Payments => "Everything related to payments - like creating and viewing payment related information are within this module", - Self::Refunds => "Refunds module encompasses everything related to refunds - like creating and viewing payment related information", - Self::MerchantAccount => "Accounts module permissions allow the user to view and update account details, configure webhooks and much more", - Self::Connectors => "All connector related actions - like configuring new connectors, viewing and updating connector configuration lies with this module", - Self::Routing => "All actions related to new, active, and past routing stacks take place here", - Self::Analytics => "Permission to view and analyse the data relating to payments, refunds, sdk etc.", - Self::Mandates => "Everything related to mandates - like creating and viewing mandate related information are within this module", - Self::Customer => "Everything related to customers - like creating and viewing customer related information are within this module", - Self::Disputes => "Everything related to disputes - like creating and viewing dispute related information are within this module", - Self::ThreeDsDecisionManager => "View and configure 3DS decision rules configured for a merchant", - Self::SurchargeDecisionManager =>"View and configure surcharge decision rules configured for a merchant", - Self::AccountCreate => "Create new account within your organization", - Self::Payouts => "Everything related to payouts - like creating and viewing payout related information are within this module", - Self::Recon => "Everything related to recon - raise requests for activating recon and generate recon auth tokens", - } - } -} - -// TODO: Deprecate once groups are stable -pub struct ModuleInfo { - pub module: PermissionModule, - pub description: &'static str, - pub permissions: Vec, -} - -impl ModuleInfo { - pub fn new(module: &PermissionModule) -> Self { - let module_name = module.clone(); - let description = module.get_module_description(); - - match module { - PermissionModule::Payments => Self { - module: module_name, - description, - permissions: get_permission_info_from_permissions(&[ - Permission::PaymentRead, - Permission::PaymentWrite, - ]), - }, - PermissionModule::Refunds => Self { - module: module_name, - description, - permissions: get_permission_info_from_permissions(&[ - Permission::RefundRead, - Permission::RefundWrite, - ]), - }, - PermissionModule::MerchantAccount => Self { - module: module_name, - description, - permissions: get_permission_info_from_permissions(&[ - Permission::MerchantAccountRead, - Permission::MerchantAccountWrite, - ]), - }, - PermissionModule::Connectors => Self { - module: module_name, - description, - permissions: get_permission_info_from_permissions(&[ - Permission::MerchantConnectorAccountRead, - Permission::MerchantConnectorAccountWrite, - ]), - }, - PermissionModule::Routing => Self { - module: module_name, - description, - permissions: get_permission_info_from_permissions(&[ - Permission::RoutingRead, - Permission::RoutingWrite, - ]), - }, - PermissionModule::Analytics => Self { - module: module_name, - description, - permissions: get_permission_info_from_permissions(&[Permission::Analytics]), - }, - PermissionModule::Mandates => Self { - module: module_name, - description, - permissions: get_permission_info_from_permissions(&[ - Permission::MandateRead, - Permission::MandateWrite, - ]), - }, - PermissionModule::Customer => Self { - module: module_name, - description, - permissions: get_permission_info_from_permissions(&[ - Permission::CustomerRead, - Permission::CustomerWrite, - ]), - }, - PermissionModule::Disputes => Self { - module: module_name, - description, - permissions: get_permission_info_from_permissions(&[ - Permission::DisputeRead, - Permission::DisputeWrite, - ]), - }, - PermissionModule::ThreeDsDecisionManager => Self { - module: module_name, - description, - permissions: get_permission_info_from_permissions(&[ - Permission::ThreeDsDecisionManagerRead, - Permission::ThreeDsDecisionManagerWrite, - ]), - }, - - PermissionModule::SurchargeDecisionManager => Self { - module: module_name, - description, - permissions: get_permission_info_from_permissions(&[ - Permission::SurchargeDecisionManagerRead, - Permission::SurchargeDecisionManagerWrite, - ]), - }, - PermissionModule::AccountCreate => Self { - module: module_name, - description, - permissions: get_permission_info_from_permissions(&[ - Permission::MerchantAccountCreate, - ]), - }, - PermissionModule::Payouts => Self { - module: module_name, - description, - permissions: get_permission_info_from_permissions(&[ - Permission::PayoutRead, - Permission::PayoutWrite, - ]), - }, - PermissionModule::Recon => Self { - module: module_name, - description, - permissions: get_permission_info_from_permissions(&[Permission::ReconAdmin]), - }, - } - } -} - +// TODO: To be deprecated fn get_group_info_from_permission_group(group: PermissionGroup) -> GroupInfo { let description = get_group_description(group); - GroupInfo { - group, - description, - permissions: get_permission_info_from_permissions(get_permissions_vec(&group)), - } + GroupInfo { group, description } } +// TODO: To be deprecated fn get_group_description(group: PermissionGroup) -> &'static str { match group { PermissionGroup::OperationsView => { @@ -219,29 +37,13 @@ fn get_group_description(group: PermissionGroup) -> &'static str { PermissionGroup::AnalyticsView => "View Analytics", PermissionGroup::UsersView => "View Users", PermissionGroup::UsersManage => "Manage and invite Users to the Team", - PermissionGroup::MerchantDetailsView => "View Merchant Details", - PermissionGroup::MerchantDetailsManage => "Create, modify and delete Merchant Details like api keys, webhooks, etc", + PermissionGroup::MerchantDetailsView | PermissionGroup::AccountView => "View Merchant Details", + PermissionGroup::MerchantDetailsManage | PermissionGroup::AccountManage => "Create, modify and delete Merchant Details like api keys, webhooks, etc", PermissionGroup::OrganizationManage => "Manage organization level tasks like create new Merchant accounts, Organization level roles, etc", - PermissionGroup::ReconOps => "View and manage reconciliation reports", - } -} - -pub fn get_parent_name(group: PermissionGroup) -> ParentGroup { - match group { - PermissionGroup::OperationsView | PermissionGroup::OperationsManage => { - ParentGroup::Operations - } - PermissionGroup::ConnectorsView | PermissionGroup::ConnectorsManage => { - ParentGroup::Connectors - } - PermissionGroup::WorkflowsView | PermissionGroup::WorkflowsManage => ParentGroup::Workflows, - PermissionGroup::AnalyticsView => ParentGroup::Analytics, - PermissionGroup::UsersView | PermissionGroup::UsersManage => ParentGroup::Users, - PermissionGroup::MerchantDetailsView | PermissionGroup::MerchantDetailsManage => { - ParentGroup::Merchant - } - PermissionGroup::OrganizationManage => ParentGroup::Organization, - PermissionGroup::ReconOps => ParentGroup::Recon, + PermissionGroup::ReconReportsView => "View and access reconciliation reports and analytics", + PermissionGroup::ReconReportsManage => "Manage reconciliation reports", + PermissionGroup::ReconOpsView => "View and access reconciliation operations", + PermissionGroup::ReconOpsManage | PermissionGroup::ReconOps => "Manage reconciliation operations", } } @@ -250,10 +52,10 @@ pub fn get_parent_group_description(group: ParentGroup) -> &'static str { ParentGroup::Operations => "Payments, Refunds, Payouts, Mandates, Disputes and Customers", ParentGroup::Connectors => "Create, modify and delete connectors like Payment Processors, Payout Processors and Fraud & Risk Manager", ParentGroup::Workflows => "Create, modify and delete Routing, 3DS Decision Manager, Surcharge Decision Manager", - ParentGroup::Analytics => "View Analytics", + ParentGroup::Analytics => "View Analytics", ParentGroup::Users => "Manage and invite Users to the Team", - ParentGroup::Merchant => "Create, modify and delete Merchant Details like api keys, webhooks, etc", - ParentGroup::Organization =>"Manage organization level tasks like create new Merchant accounts, Organization level roles, etc", - ParentGroup::Recon => "View and manage reconciliation reports", + ParentGroup::Account => "Create, modify and delete Merchant Details like api keys, webhooks, etc", + ParentGroup::ReconOps => "View, manage reconciliation operations like upload and process files, run reconciliation etc", + ParentGroup::ReconReports => "View, manage reconciliation reports and analytics", } } diff --git a/crates/router/src/services/authorization/permission_groups.rs b/crates/router/src/services/authorization/permission_groups.rs index aafc9cee9430..ceb943950d52 100644 --- a/crates/router/src/services/authorization/permission_groups.rs +++ b/crates/router/src/services/authorization/permission_groups.rs @@ -1,97 +1,198 @@ -use common_enums::PermissionGroup; - -use super::permissions::Permission; - -pub fn get_permissions_vec(permission_group: &PermissionGroup) -> &[Permission] { - match permission_group { - PermissionGroup::OperationsView => &OPERATIONS_VIEW, - PermissionGroup::OperationsManage => &OPERATIONS_MANAGE, - PermissionGroup::ConnectorsView => &CONNECTORS_VIEW, - PermissionGroup::ConnectorsManage => &CONNECTORS_MANAGE, - PermissionGroup::WorkflowsView => &WORKFLOWS_VIEW, - PermissionGroup::WorkflowsManage => &WORKFLOWS_MANAGE, - PermissionGroup::AnalyticsView => &ANALYTICS_VIEW, - PermissionGroup::UsersView => &USERS_VIEW, - PermissionGroup::UsersManage => &USERS_MANAGE, - PermissionGroup::MerchantDetailsView => &MERCHANT_DETAILS_VIEW, - PermissionGroup::MerchantDetailsManage => &MERCHANT_DETAILS_MANAGE, - PermissionGroup::OrganizationManage => &ORGANIZATION_MANAGE, - PermissionGroup::ReconOps => &RECON, +use std::collections::HashMap; + +use common_enums::{EntityType, ParentGroup, PermissionGroup, PermissionScope, Resource}; +use strum::IntoEnumIterator; + +use super::permissions::{self, ResourceExt}; + +pub trait PermissionGroupExt { + fn scope(&self) -> PermissionScope; + fn parent(&self) -> ParentGroup; + fn resources(&self) -> Vec; + fn accessible_groups(&self) -> Vec; +} + +impl PermissionGroupExt for PermissionGroup { + fn scope(&self) -> PermissionScope { + match self { + Self::OperationsView + | Self::ConnectorsView + | Self::WorkflowsView + | Self::AnalyticsView + | Self::UsersView + | Self::MerchantDetailsView + | Self::AccountView + | Self::ReconOpsView + | Self::ReconReportsView => PermissionScope::Read, + + Self::OperationsManage + | Self::ConnectorsManage + | Self::WorkflowsManage + | Self::UsersManage + | Self::MerchantDetailsManage + | Self::OrganizationManage + | Self::AccountManage + | Self::ReconOpsManage + | Self::ReconOps + | Self::ReconReportsManage => PermissionScope::Write, + } + } + + fn parent(&self) -> ParentGroup { + match self { + Self::OperationsView | Self::OperationsManage => ParentGroup::Operations, + Self::ConnectorsView | Self::ConnectorsManage => ParentGroup::Connectors, + Self::WorkflowsView | Self::WorkflowsManage => ParentGroup::Workflows, + Self::AnalyticsView => ParentGroup::Analytics, + Self::UsersView | Self::UsersManage => ParentGroup::Users, + Self::MerchantDetailsView + | Self::OrganizationManage + | Self::MerchantDetailsManage + | Self::AccountView + | Self::AccountManage => ParentGroup::Account, + Self::ReconOpsView | Self::ReconOpsManage | Self::ReconOps => ParentGroup::ReconOps, + Self::ReconReportsView | Self::ReconReportsManage => ParentGroup::ReconReports, + } + } + + fn resources(&self) -> Vec { + self.parent().resources() + } + + fn accessible_groups(&self) -> Vec { + match self { + Self::OperationsView => vec![Self::OperationsView, Self::ConnectorsView], + Self::OperationsManage => vec![ + Self::OperationsView, + Self::OperationsManage, + Self::ConnectorsView, + ], + + Self::ConnectorsView => vec![Self::ConnectorsView], + Self::ConnectorsManage => vec![Self::ConnectorsView, Self::ConnectorsManage], + + Self::WorkflowsView => vec![Self::WorkflowsView, Self::ConnectorsView], + Self::WorkflowsManage => vec![ + Self::WorkflowsView, + Self::WorkflowsManage, + Self::ConnectorsView, + ], + + Self::AnalyticsView => vec![Self::AnalyticsView, Self::OperationsView], + + Self::UsersView => vec![Self::UsersView], + Self::UsersManage => { + vec![Self::UsersView, Self::UsersManage] + } + + Self::ReconOpsView => vec![Self::ReconOpsView], + Self::ReconOpsManage | Self::ReconOps => vec![Self::ReconOpsView, Self::ReconOpsManage], + + Self::ReconReportsView => vec![Self::ReconReportsView], + Self::ReconReportsManage => vec![Self::ReconReportsView, Self::ReconReportsManage], + + Self::MerchantDetailsView => vec![Self::MerchantDetailsView], + Self::MerchantDetailsManage => { + vec![Self::MerchantDetailsView, Self::MerchantDetailsManage] + } + + Self::OrganizationManage => vec![Self::OrganizationManage], + + Self::AccountView => vec![Self::AccountView], + Self::AccountManage => vec![Self::AccountView, Self::AccountManage], + } } } -pub static OPERATIONS_VIEW: [Permission; 8] = [ - Permission::PaymentRead, - Permission::RefundRead, - Permission::MandateRead, - Permission::DisputeRead, - Permission::CustomerRead, - Permission::GenerateReport, - Permission::PayoutRead, - Permission::MerchantAccountRead, -]; +pub trait ParentGroupExt { + fn resources(&self) -> Vec; + fn get_descriptions_for_groups( + entity_type: EntityType, + groups: Vec, + ) -> HashMap; +} -pub static OPERATIONS_MANAGE: [Permission; 7] = [ - Permission::PaymentWrite, - Permission::RefundWrite, - Permission::MandateWrite, - Permission::DisputeWrite, - Permission::CustomerWrite, - Permission::PayoutWrite, - Permission::MerchantAccountRead, -]; +impl ParentGroupExt for ParentGroup { + fn resources(&self) -> Vec { + match self { + Self::Operations => OPERATIONS.to_vec(), + Self::Connectors => CONNECTORS.to_vec(), + Self::Workflows => WORKFLOWS.to_vec(), + Self::Analytics => ANALYTICS.to_vec(), + Self::Users => USERS.to_vec(), + Self::Account => ACCOUNT.to_vec(), + Self::ReconOps => RECON_OPS.to_vec(), + Self::ReconReports => RECON_REPORTS.to_vec(), + } + } -pub static CONNECTORS_VIEW: [Permission; 2] = [ - Permission::MerchantConnectorAccountRead, - Permission::MerchantAccountRead, -]; + fn get_descriptions_for_groups( + entity_type: EntityType, + groups: Vec, + ) -> HashMap { + Self::iter() + .filter_map(|parent| { + let scopes = groups + .iter() + .filter(|group| group.parent() == parent) + .map(|group| group.scope()) + .max()?; -pub static CONNECTORS_MANAGE: [Permission; 2] = [ - Permission::MerchantConnectorAccountWrite, - Permission::MerchantAccountRead, -]; + let resources = parent + .resources() + .iter() + .filter(|res| res.entities().iter().any(|entity| entity <= &entity_type)) + .map(|res| permissions::get_resource_name(*res, entity_type)) + .collect::>() + .join(", "); -pub static WORKFLOWS_VIEW: [Permission; 5] = [ - Permission::RoutingRead, - Permission::ThreeDsDecisionManagerRead, - Permission::SurchargeDecisionManagerRead, - Permission::MerchantConnectorAccountRead, - Permission::MerchantAccountRead, -]; + Some(( + parent, + format!("{} {}", permissions::get_scope_name(scopes), resources), + )) + }) + .collect() + } +} -pub static WORKFLOWS_MANAGE: [Permission; 5] = [ - Permission::RoutingWrite, - Permission::ThreeDsDecisionManagerWrite, - Permission::SurchargeDecisionManagerWrite, - Permission::MerchantConnectorAccountRead, - Permission::MerchantAccountRead, +pub static OPERATIONS: [Resource; 8] = [ + Resource::Payment, + Resource::Refund, + Resource::Mandate, + Resource::Dispute, + Resource::Customer, + Resource::Payout, + Resource::Report, + Resource::Account, ]; -pub static ANALYTICS_VIEW: [Permission; 3] = [ - Permission::Analytics, - Permission::GenerateReport, - Permission::MerchantAccountRead, +pub static CONNECTORS: [Resource; 2] = [Resource::Connector, Resource::Account]; + +pub static WORKFLOWS: [Resource; 4] = [ + Resource::Routing, + Resource::ThreeDsDecisionManager, + Resource::SurchargeDecisionManager, + Resource::Account, ]; -pub static USERS_VIEW: [Permission; 2] = [Permission::UsersRead, Permission::MerchantAccountRead]; +pub static ANALYTICS: [Resource; 3] = [Resource::Analytics, Resource::Report, Resource::Account]; -pub static USERS_MANAGE: [Permission; 2] = - [Permission::UsersWrite, Permission::MerchantAccountRead]; +pub static USERS: [Resource; 2] = [Resource::User, Resource::Account]; -pub static MERCHANT_DETAILS_VIEW: [Permission; 1] = [Permission::MerchantAccountRead]; +pub static ACCOUNT: [Resource; 3] = [Resource::Account, Resource::ApiKey, Resource::WebhookEvent]; -pub static MERCHANT_DETAILS_MANAGE: [Permission; 6] = [ - Permission::MerchantAccountWrite, - Permission::ApiKeyRead, - Permission::ApiKeyWrite, - Permission::MerchantAccountRead, - Permission::WebhookEventRead, - Permission::WebhookEventWrite, +pub static RECON_OPS: [Resource; 7] = [ + Resource::ReconToken, + Resource::ReconFiles, + Resource::ReconUpload, + Resource::RunRecon, + Resource::ReconConfig, + Resource::ReconAndSettlementAnalytics, + Resource::ReconReports, ]; -pub static ORGANIZATION_MANAGE: [Permission; 2] = [ - Permission::MerchantAccountCreate, - Permission::MerchantAccountRead, +pub static RECON_REPORTS: [Resource; 3] = [ + Resource::ReconToken, + Resource::ReconAndSettlementAnalytics, + Resource::ReconReports, ]; - -pub static RECON: [Permission; 1] = [Permission::ReconAdmin]; diff --git a/crates/router/src/services/authorization/permissions.rs b/crates/router/src/services/authorization/permissions.rs index 2f0617557caf..04d3d4367360 100644 --- a/crates/router/src/services/authorization/permissions.rs +++ b/crates/router/src/services/authorization/permissions.rs @@ -1,84 +1,137 @@ -use strum::Display; +use common_enums::{EntityType, PermissionScope, Resource}; +use router_derive::generate_permissions; -#[derive( - PartialEq, Display, Clone, Debug, Copy, Eq, Hash, serde::Deserialize, serde::Serialize, -)] -pub enum Permission { - PaymentRead, - PaymentWrite, - RefundRead, - RefundWrite, - ApiKeyRead, - ApiKeyWrite, - MerchantAccountRead, - MerchantAccountWrite, - MerchantConnectorAccountRead, - MerchantConnectorAccountWrite, - RoutingRead, - RoutingWrite, - DisputeRead, - DisputeWrite, - MandateRead, - MandateWrite, - CustomerRead, - CustomerWrite, - Analytics, - ThreeDsDecisionManagerWrite, - ThreeDsDecisionManagerRead, - SurchargeDecisionManagerWrite, - SurchargeDecisionManagerRead, - UsersRead, - UsersWrite, - MerchantAccountCreate, - WebhookEventRead, - WebhookEventWrite, - PayoutRead, - PayoutWrite, - GenerateReport, - ReconAdmin, +generate_permissions! { + permissions: [ + Payment: { + scopes: [Read, Write], + entities: [Profile, Merchant] + }, + Refund: { + scopes: [Read, Write], + entities: [Profile, Merchant] + }, + Dispute: { + scopes: [Read, Write], + entities: [Profile, Merchant] + }, + Mandate: { + scopes: [Read, Write], + entities: [Merchant] + }, + Customer: { + scopes: [Read, Write], + entities: [Merchant] + }, + Payout: { + scopes: [Read], + entities: [Profile, Merchant] + }, + ApiKey: { + scopes: [Read, Write], + entities: [Merchant] + }, + Account: { + scopes: [Read, Write], + entities: [Profile, Merchant, Organization, Tenant] + }, + Connector: { + scopes: [Read, Write], + entities: [Profile, Merchant] + }, + Routing: { + scopes: [Read, Write], + entities: [Profile, Merchant] + }, + ThreeDsDecisionManager: { + scopes: [Read, Write], + entities: [Merchant] + }, + SurchargeDecisionManager: { + scopes: [Read, Write], + entities: [Merchant] + }, + Analytics: { + scopes: [Read], + entities: [Profile, Merchant, Organization] + }, + Report: { + scopes: [Read], + entities: [Profile, Merchant, Organization] + }, + User: { + scopes: [Read, Write], + entities: [Profile, Merchant] + }, + WebhookEvent: { + scopes: [Read, Write], + entities: [Merchant] + }, + ReconToken: { + scopes: [Read], + entities: [Merchant] + }, + ReconFiles: { + scopes: [Read, Write], + entities: [Merchant] + }, + ReconAndSettlementAnalytics: { + scopes: [Read], + entities: [Merchant] + }, + ReconUpload: { + scopes: [Read, Write], + entities: [Merchant] + }, + ReconReports: { + scopes: [Read, Write], + entities: [Merchant] + }, + RunRecon: { + scopes: [Read, Write], + entities: [Merchant] + }, + ReconConfig: { + scopes: [Read, Write], + entities: [Merchant] + }, + ] } -impl Permission { - pub fn get_permission_description(&self) -> &'static str { - match self { - Self::PaymentRead => "View all payments", - Self::PaymentWrite => "Create payment, download payments data", - Self::RefundRead => "View all refunds", - Self::RefundWrite => "Create refund, download refunds data", - Self::ApiKeyRead => "View API keys", - Self::ApiKeyWrite => "Create and update API keys", - Self::MerchantAccountRead => "View merchant account details", - Self::MerchantAccountWrite => { - "Update merchant account details, configure webhooks, manage api keys" - } - Self::MerchantConnectorAccountRead => "View connectors configured", - Self::MerchantConnectorAccountWrite => { - "Create, update, verify and delete connector configurations" - } - Self::RoutingRead => "View routing configuration", - Self::RoutingWrite => "Create and activate routing configurations", - Self::DisputeRead => "View disputes", - Self::DisputeWrite => "Create and update disputes", - Self::MandateRead => "View mandates", - Self::MandateWrite => "Create and update mandates", - Self::CustomerRead => "View customers", - Self::CustomerWrite => "Create, update and delete customers", - Self::Analytics => "Access to analytics module", - Self::ThreeDsDecisionManagerWrite => "Create and update 3DS decision rules", - Self::ThreeDsDecisionManagerRead => { - "View all 3DS decision rules configured for a merchant" - } - Self::SurchargeDecisionManagerWrite => "Create and update the surcharge decision rules", - Self::SurchargeDecisionManagerRead => "View all the surcharge decision rules", - Self::UsersRead => "View all the users for a merchant", - Self::UsersWrite => "Invite users, assign and update roles", - Self::MerchantAccountCreate => "Create merchant account", - Self::WebhookEventRead => "View webhook events", - Self::WebhookEventWrite => "Trigger retries for webhook events", - Self::PayoutRead => "View all payouts", - Self::PayoutWrite => "Create payout, download payout data", - Self::GenerateReport => "Generate reports for payments, refunds and disputes", - Self::ReconAdmin => "View and manage reconciliation reports", - } +pub fn get_resource_name(resource: Resource, entity_type: EntityType) -> &'static str { + match (resource, entity_type) { + (Resource::Payment, _) => "Payments", + (Resource::Refund, _) => "Refunds", + (Resource::Dispute, _) => "Disputes", + (Resource::Mandate, _) => "Mandates", + (Resource::Customer, _) => "Customers", + (Resource::Payout, _) => "Payouts", + (Resource::ApiKey, _) => "Api Keys", + (Resource::Connector, _) => "Payment Processors, Payout Processors, Fraud & Risk Managers", + (Resource::Routing, _) => "Routing", + (Resource::ThreeDsDecisionManager, _) => "3DS Decision Manager", + (Resource::SurchargeDecisionManager, _) => "Surcharge Decision Manager", + (Resource::Analytics, _) => "Analytics", + (Resource::Report, _) => "Operation Reports", + (Resource::User, _) => "Users", + (Resource::WebhookEvent, _) => "Webhook Events", + (Resource::ReconUpload, _) => "Reconciliation File Upload", + (Resource::RunRecon, _) => "Run Reconciliation Process", + (Resource::ReconConfig, _) => "Reconciliation Configurations", + (Resource::ReconToken, _) => "Generate & Verify Reconciliation Token", + (Resource::ReconFiles, _) => "Reconciliation Process Manager", + (Resource::ReconReports, _) => "Reconciliation Reports", + (Resource::ReconAndSettlementAnalytics, _) => "Reconciliation Analytics", + (Resource::Account, EntityType::Profile) => "Business Profile Account", + (Resource::Account, EntityType::Merchant) => "Merchant Account", + (Resource::Account, EntityType::Organization) => "Organization Account", + (Resource::Account, EntityType::Tenant) => "Tenant Account", + } +} + +pub fn get_scope_name(scope: PermissionScope) -> &'static str { + match scope { + PermissionScope::Read => "View", + PermissionScope::Write => "View and Manage", } } diff --git a/crates/router/src/services/authorization/roles.rs b/crates/router/src/services/authorization/roles.rs index 19383f010f2f..c9c64b76143d 100644 --- a/crates/router/src/services/authorization/roles.rs +++ b/crates/router/src/services/authorization/roles.rs @@ -1,9 +1,15 @@ +#[cfg(feature = "recon")] +use std::collections::HashMap; use std::collections::HashSet; -use common_enums::{EntityType, PermissionGroup, RoleScope}; +#[cfg(feature = "recon")] +use api_models::enums::ReconPermissionScope; +use common_enums::{EntityType, PermissionGroup, Resource, RoleScope}; use common_utils::{errors::CustomResult, id_type}; -use super::{permission_groups::get_permissions_vec, permissions::Permission}; +#[cfg(feature = "recon")] +use super::permission_groups::{RECON_OPS, RECON_REPORTS}; +use super::{permission_groups::PermissionGroupExt, permissions::Permission}; use crate::{core::errors, routes::SessionState}; pub mod predefined_roles; @@ -30,8 +36,13 @@ impl RoleInfo { &self.role_name } - pub fn get_permission_groups(&self) -> &Vec { - &self.groups + pub fn get_permission_groups(&self) -> Vec { + self.groups + .iter() + .flat_map(|group| group.accessible_groups()) + .collect::>() + .into_iter() + .collect() } pub fn get_scope(&self) -> RoleScope { @@ -58,20 +69,54 @@ impl RoleInfo { self.is_updatable } - pub fn get_permissions_set(&self) -> HashSet { - self.groups + pub fn get_resources_set(&self) -> HashSet { + self.get_permission_groups() .iter() - .flat_map(|group| get_permissions_vec(group).iter().copied()) + .flat_map(|group| group.resources()) .collect() } - pub fn check_permission_exists(&self, required_permission: &Permission) -> bool { - self.groups + pub fn check_permission_exists(&self, required_permission: Permission) -> bool { + required_permission.entity_type() <= self.entity_type + && self.get_permission_groups().iter().any(|group| { + required_permission.scope() <= group.scope() + && group.resources().contains(&required_permission.resource()) + }) + } + + #[cfg(feature = "recon")] + pub fn get_recon_acl(&self) -> HashMap { + let mut acl: HashMap = HashMap::new(); + let mut recon_resources = RECON_OPS.to_vec(); + recon_resources.extend(RECON_REPORTS); + let recon_internal_resources = [Resource::ReconToken]; + self.get_permission_groups() .iter() - .any(|group| get_permissions_vec(group).contains(required_permission)) + .for_each(|permission_group| { + permission_group.resources().iter().for_each(|resource| { + if recon_resources.contains(resource) + && !recon_internal_resources.contains(resource) + { + let scope = match resource { + Resource::ReconAndSettlementAnalytics => ReconPermissionScope::Read, + _ => ReconPermissionScope::from(permission_group.scope()), + }; + acl.entry(*resource) + .and_modify(|curr_scope| { + *curr_scope = if (*curr_scope) < scope { + scope + } else { + *curr_scope + } + }) + .or_insert(scope); + } + }) + }); + acl } - pub async fn from_role_id_in_merchant_scope( + pub async fn from_role_id_in_lineage( state: &SessionState, role_id: &str, merchant_id: &id_type::MerchantId, @@ -81,14 +126,14 @@ impl RoleInfo { Ok(role.clone()) } else { state - .store - .find_role_by_role_id_in_merchant_scope(role_id, merchant_id, org_id) + .global_store + .find_role_by_role_id_in_lineage(role_id, merchant_id, org_id) .await .map(Self::from) } } - pub async fn from_role_id_in_org_scope( + pub async fn from_role_id_and_org_id( state: &SessionState, role_id: &str, org_id: &id_type::OrganizationId, @@ -97,8 +142,8 @@ impl RoleInfo { Ok(role.clone()) } else { state - .store - .find_role_by_role_id_in_org_scope(role_id, org_id) + .global_store + .find_by_role_id_and_org_id(role_id, org_id) .await .map(Self::from) } @@ -112,7 +157,7 @@ impl From for RoleInfo { role_name: role.role_name, groups: role.groups.into_iter().map(Into::into).collect(), scope: role.scope, - entity_type: role.entity_type.unwrap_or(EntityType::Merchant), + entity_type: role.entity_type, is_invitable: true, is_deletable: true, is_updatable: true, diff --git a/crates/router/src/services/authorization/roles/predefined_roles.rs b/crates/router/src/services/authorization/roles/predefined_roles.rs index b271abd1effc..fe7498de9eba 100644 --- a/crates/router/src/services/authorization/roles/predefined_roles.rs +++ b/crates/router/src/services/authorization/roles/predefined_roles.rs @@ -24,9 +24,14 @@ pub static PREDEFINED_ROLES: Lazy> = Lazy::new(| PermissionGroup::UsersView, PermissionGroup::UsersManage, PermissionGroup::MerchantDetailsView, + PermissionGroup::AccountView, PermissionGroup::MerchantDetailsManage, + PermissionGroup::AccountManage, PermissionGroup::OrganizationManage, - PermissionGroup::ReconOps, + PermissionGroup::ReconOpsView, + PermissionGroup::ReconOpsManage, + PermissionGroup::ReconReportsView, + PermissionGroup::ReconReportsManage, ], role_id: common_utils::consts::ROLE_ID_INTERNAL_ADMIN.to_string(), role_name: "internal_admin".to_string(), @@ -48,6 +53,9 @@ pub static PREDEFINED_ROLES: Lazy> = Lazy::new(| PermissionGroup::AnalyticsView, PermissionGroup::UsersView, PermissionGroup::MerchantDetailsView, + PermissionGroup::AccountView, + PermissionGroup::ReconOpsView, + PermissionGroup::ReconReportsView, ], role_id: common_utils::consts::ROLE_ID_INTERNAL_VIEW_ONLY_USER.to_string(), role_name: "internal_view_only".to_string(), @@ -60,7 +68,42 @@ pub static PREDEFINED_ROLES: Lazy> = Lazy::new(| }, ); - // Merchant Roles + // Tenant Roles + roles.insert( + common_utils::consts::ROLE_ID_TENANT_ADMIN, + RoleInfo { + groups: vec![ + PermissionGroup::OperationsView, + PermissionGroup::OperationsManage, + PermissionGroup::ConnectorsView, + PermissionGroup::ConnectorsManage, + PermissionGroup::WorkflowsView, + PermissionGroup::WorkflowsManage, + PermissionGroup::AnalyticsView, + PermissionGroup::UsersView, + PermissionGroup::UsersManage, + PermissionGroup::MerchantDetailsView, + PermissionGroup::AccountView, + PermissionGroup::MerchantDetailsManage, + PermissionGroup::AccountManage, + PermissionGroup::OrganizationManage, + PermissionGroup::ReconOpsView, + PermissionGroup::ReconOpsManage, + PermissionGroup::ReconReportsView, + PermissionGroup::ReconReportsManage, + ], + role_id: common_utils::consts::ROLE_ID_TENANT_ADMIN.to_string(), + role_name: "tenant_admin".to_string(), + scope: RoleScope::Organization, + entity_type: EntityType::Tenant, + is_invitable: false, + is_deletable: false, + is_updatable: false, + is_internal: false, + }, + ); + + // Organization Roles roles.insert( common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN, RoleInfo { @@ -75,17 +118,22 @@ pub static PREDEFINED_ROLES: Lazy> = Lazy::new(| PermissionGroup::UsersView, PermissionGroup::UsersManage, PermissionGroup::MerchantDetailsView, + PermissionGroup::AccountView, PermissionGroup::MerchantDetailsManage, + PermissionGroup::AccountManage, PermissionGroup::OrganizationManage, - PermissionGroup::ReconOps, + PermissionGroup::ReconOpsView, + PermissionGroup::ReconOpsManage, + PermissionGroup::ReconReportsView, + PermissionGroup::ReconReportsManage, ], role_id: common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN.to_string(), role_name: "organization_admin".to_string(), scope: RoleScope::Organization, entity_type: EntityType::Organization, - is_invitable: false, - is_deletable: false, - is_updatable: false, + is_invitable: true, + is_deletable: true, + is_updatable: true, is_internal: false, }, ); @@ -105,8 +153,13 @@ pub static PREDEFINED_ROLES: Lazy> = Lazy::new(| PermissionGroup::UsersView, PermissionGroup::UsersManage, PermissionGroup::MerchantDetailsView, + PermissionGroup::AccountView, PermissionGroup::MerchantDetailsManage, - PermissionGroup::ReconOps, + PermissionGroup::AccountManage, + PermissionGroup::ReconOpsView, + PermissionGroup::ReconOpsManage, + PermissionGroup::ReconReportsView, + PermissionGroup::ReconReportsManage, ], role_id: consts::user_role::ROLE_ID_MERCHANT_ADMIN.to_string(), role_name: "merchant_admin".to_string(), @@ -128,6 +181,9 @@ pub static PREDEFINED_ROLES: Lazy> = Lazy::new(| PermissionGroup::AnalyticsView, PermissionGroup::UsersView, PermissionGroup::MerchantDetailsView, + PermissionGroup::AccountView, + PermissionGroup::ReconOpsView, + PermissionGroup::ReconReportsView, ], role_id: consts::user_role::ROLE_ID_MERCHANT_VIEW_ONLY.to_string(), role_name: "merchant_view_only".to_string(), @@ -148,6 +204,7 @@ pub static PREDEFINED_ROLES: Lazy> = Lazy::new(| PermissionGroup::UsersView, PermissionGroup::UsersManage, PermissionGroup::MerchantDetailsView, + PermissionGroup::AccountView, ], role_id: consts::user_role::ROLE_ID_MERCHANT_IAM_ADMIN.to_string(), role_name: "merchant_iam".to_string(), @@ -168,7 +225,11 @@ pub static PREDEFINED_ROLES: Lazy> = Lazy::new(| PermissionGroup::AnalyticsView, PermissionGroup::UsersView, PermissionGroup::MerchantDetailsView, + PermissionGroup::AccountView, PermissionGroup::MerchantDetailsManage, + PermissionGroup::AccountManage, + PermissionGroup::ReconOpsView, + PermissionGroup::ReconReportsView, ], role_id: consts::user_role::ROLE_ID_MERCHANT_DEVELOPER.to_string(), role_name: "merchant_developer".to_string(), @@ -191,6 +252,10 @@ pub static PREDEFINED_ROLES: Lazy> = Lazy::new(| PermissionGroup::AnalyticsView, PermissionGroup::UsersView, PermissionGroup::MerchantDetailsView, + PermissionGroup::AccountView, + PermissionGroup::ReconOpsView, + PermissionGroup::ReconOpsManage, + PermissionGroup::ReconReportsView, ], role_id: consts::user_role::ROLE_ID_MERCHANT_OPERATOR.to_string(), role_name: "merchant_operator".to_string(), @@ -210,6 +275,9 @@ pub static PREDEFINED_ROLES: Lazy> = Lazy::new(| PermissionGroup::AnalyticsView, PermissionGroup::UsersView, PermissionGroup::MerchantDetailsView, + PermissionGroup::AccountView, + PermissionGroup::ReconOpsView, + PermissionGroup::ReconReportsView, ], role_id: consts::user_role::ROLE_ID_MERCHANT_CUSTOMER_SUPPORT.to_string(), role_name: "customer_support".to_string(), @@ -237,7 +305,9 @@ pub static PREDEFINED_ROLES: Lazy> = Lazy::new(| PermissionGroup::UsersView, PermissionGroup::UsersManage, PermissionGroup::MerchantDetailsView, + PermissionGroup::AccountView, PermissionGroup::MerchantDetailsManage, + PermissionGroup::AccountManage, ], role_id: consts::user_role::ROLE_ID_PROFILE_ADMIN.to_string(), role_name: "profile_admin".to_string(), @@ -259,6 +329,7 @@ pub static PREDEFINED_ROLES: Lazy> = Lazy::new(| PermissionGroup::AnalyticsView, PermissionGroup::UsersView, PermissionGroup::MerchantDetailsView, + PermissionGroup::AccountView, ], role_id: consts::user_role::ROLE_ID_PROFILE_VIEW_ONLY.to_string(), role_name: "profile_view_only".to_string(), @@ -279,6 +350,7 @@ pub static PREDEFINED_ROLES: Lazy> = Lazy::new(| PermissionGroup::UsersView, PermissionGroup::UsersManage, PermissionGroup::MerchantDetailsView, + PermissionGroup::AccountView, ], role_id: consts::user_role::ROLE_ID_PROFILE_IAM_ADMIN.to_string(), role_name: "profile_iam".to_string(), @@ -299,7 +371,9 @@ pub static PREDEFINED_ROLES: Lazy> = Lazy::new(| PermissionGroup::AnalyticsView, PermissionGroup::UsersView, PermissionGroup::MerchantDetailsView, + PermissionGroup::AccountView, PermissionGroup::MerchantDetailsManage, + PermissionGroup::AccountManage, ], role_id: consts::user_role::ROLE_ID_PROFILE_DEVELOPER.to_string(), role_name: "profile_developer".to_string(), @@ -322,6 +396,7 @@ pub static PREDEFINED_ROLES: Lazy> = Lazy::new(| PermissionGroup::AnalyticsView, PermissionGroup::UsersView, PermissionGroup::MerchantDetailsView, + PermissionGroup::AccountView, ], role_id: consts::user_role::ROLE_ID_PROFILE_OPERATOR.to_string(), role_name: "profile_operator".to_string(), @@ -341,6 +416,7 @@ pub static PREDEFINED_ROLES: Lazy> = Lazy::new(| PermissionGroup::AnalyticsView, PermissionGroup::UsersView, PermissionGroup::MerchantDetailsView, + PermissionGroup::AccountView, ], role_id: consts::user_role::ROLE_ID_PROFILE_CUSTOMER_SUPPORT.to_string(), role_name: "profile_customer_support".to_string(), diff --git a/crates/router/src/services/connector_integration_interface.rs b/crates/router/src/services/connector_integration_interface.rs index a597ddfe1540..5903c8e9cc0d 100644 --- a/crates/router/src/services/connector_integration_interface.rs +++ b/crates/router/src/services/connector_integration_interface.rs @@ -1,10 +1,15 @@ use common_utils::{crypto, errors::CustomResult, request::Request}; -use hyperswitch_domain_models::{router_data::RouterData, router_data_v2::RouterDataV2}; +use hyperswitch_domain_models::{ + router_data::RouterData, + router_data_v2::RouterDataV2, + router_response_types::{ConnectorInfo, SupportedPaymentMethods}, +}; use hyperswitch_interfaces::{ - authentication::ExternalAuthenticationPayload, connector_integration_v2::ConnectorIntegrationV2, + authentication::ExternalAuthenticationPayload, + connector_integration_v2::ConnectorIntegrationV2, webhooks::IncomingWebhookFlowError, }; -use super::{BoxedConnectorIntegrationV2, ConnectorValidation}; +use super::{BoxedConnectorIntegrationV2, ConnectorSpecifications, ConnectorValidation}; use crate::{ core::payments, errors, @@ -279,11 +284,12 @@ impl api::IncomingWebhook for ConnectorEnum { fn get_webhook_api_response( &self, request: &IncomingWebhookRequestDetails<'_>, + error_kind: Option, ) -> CustomResult, errors::ConnectorError> { match self { - Self::Old(connector) => connector.get_webhook_api_response(request), - Self::New(connector) => connector.get_webhook_api_response(request), + Self::Old(connector) => connector.get_webhook_api_response(request, error_kind), + Self::New(connector) => connector.get_webhook_api_response(request, error_kind), } } @@ -306,6 +312,32 @@ impl api::IncomingWebhook for ConnectorEnum { Self::New(connector) => connector.get_external_authentication_details(request), } } + + fn get_mandate_details( + &self, + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult< + Option, + errors::ConnectorError, + > { + match self { + Self::Old(connector) => connector.get_mandate_details(request), + Self::New(connector) => connector.get_mandate_details(request), + } + } + + fn get_network_txn_id( + &self, + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult< + Option, + errors::ConnectorError, + > { + match self { + Self::Old(connector) => connector.get_network_txn_id(request), + Self::New(connector) => connector.get_network_txn_id(request), + } + } } impl api::ConnectorTransactionId for ConnectorEnum { @@ -335,14 +367,23 @@ impl ConnectorRedirectResponse for ConnectorEnum { } impl ConnectorValidation for ConnectorEnum { - fn validate_capture_method( + fn validate_connector_against_payment_request( &self, capture_method: Option, + payment_method: common_enums::PaymentMethod, pmt: Option, ) -> CustomResult<(), errors::ConnectorError> { match self { - Self::Old(connector) => connector.validate_capture_method(capture_method, pmt), - Self::New(connector) => connector.validate_capture_method(capture_method, pmt), + Self::Old(connector) => connector.validate_connector_against_payment_request( + capture_method, + payment_method, + pmt, + ), + Self::New(connector) => connector.validate_connector_against_payment_request( + capture_method, + payment_method, + pmt, + ), } } @@ -388,6 +429,31 @@ impl ConnectorValidation for ConnectorEnum { } } +impl ConnectorSpecifications for ConnectorEnum { + fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> { + match self { + Self::Old(connector) => connector.get_supported_payment_methods(), + Self::New(connector) => connector.get_supported_payment_methods(), + } + } + + /// Supported webhooks flows + fn get_supported_webhook_flows(&self) -> Option<&'static [common_enums::EventClass]> { + match self { + Self::Old(connector) => connector.get_supported_webhook_flows(), + Self::New(connector) => connector.get_supported_webhook_flows(), + } + } + + /// Details related to connector + fn get_connector_about(&self) -> Option<&'static ConnectorInfo> { + match self { + Self::Old(connector) => connector.get_connector_about(), + Self::New(connector) => connector.get_connector_about(), + } + } +} + impl api::ConnectorCommon for ConnectorEnum { fn id(&self) -> &'static str { match self { diff --git a/crates/router/src/services/conversion_impls.rs b/crates/router/src/services/conversion_impls.rs index 22930916093d..9bb88cf4ecc0 100644 --- a/crates/router/src/services/conversion_impls.rs +++ b/crates/router/src/services/conversion_impls.rs @@ -1,3 +1,4 @@ +use error_stack::ResultExt; #[cfg(feature = "frm")] use hyperswitch_domain_models::router_data_v2::flow_common_types::FrmFlowData; #[cfg(feature = "payouts")] @@ -8,7 +9,8 @@ use hyperswitch_domain_models::{ router_data_v2::{ flow_common_types::{ AccessTokenFlowData, DisputesFlowData, ExternalAuthenticationFlowData, FilesFlowData, - MandateRevokeFlowData, PaymentFlowData, RefundFlowData, WebhookSourceVerifyData, + MandateRevokeFlowData, PaymentFlowData, RefundFlowData, UasFlowData, + WebhookSourceVerifyData, }, RouterDataV2, }, @@ -39,7 +41,6 @@ fn get_default_router_data( payment_method: common_enums::PaymentMethod::default(), connector_auth_type: router_data::ConnectorAuthType::default(), description: None, - return_url: None, address: PaymentAddress::default(), auth_type: common_enums::AuthenticationType::default(), connector_meta_data: None, @@ -76,6 +77,9 @@ fn get_default_router_data( integrity_check: Ok(()), additional_merchant_data: None, header_payload: None, + connector_mandate_request_reference_id: None, + authentication_id: None, + psd2_sca_exemption_type: None, } } @@ -126,7 +130,6 @@ impl RouterDataConversion for PaymentF status: old_router_data.status, payment_method: old_router_data.payment_method, description: old_router_data.description.clone(), - return_url: old_router_data.return_url.clone(), address: old_router_data.address.clone(), auth_type: old_router_data.auth_type, connector_meta_data: old_router_data.connector_meta_data.clone(), @@ -172,7 +175,6 @@ impl RouterDataConversion for PaymentF status, payment_method, description, - return_url, address, auth_type, connector_meta_data, @@ -204,7 +206,6 @@ impl RouterDataConversion for PaymentF router_data.status = status; router_data.payment_method = payment_method; router_data.description = description; - router_data.return_url = return_url; router_data.address = address; router_data.auth_type = auth_type; router_data.connector_meta_data = connector_meta_data; @@ -243,7 +244,6 @@ impl RouterDataConversion for RefundFl attempt_id: old_router_data.attempt_id.clone(), status: old_router_data.status, payment_method: old_router_data.payment_method, - return_url: old_router_data.return_url.clone(), connector_meta_data: old_router_data.connector_meta_data.clone(), amount_captured: old_router_data.amount_captured, minor_amount_captured: old_router_data.minor_amount_captured, @@ -276,7 +276,6 @@ impl RouterDataConversion for RefundFl attempt_id, status, payment_method, - return_url, connector_meta_data, amount_captured, minor_amount_captured, @@ -291,7 +290,6 @@ impl RouterDataConversion for RefundFl router_data.attempt_id = attempt_id; router_data.status = status; router_data.payment_method = payment_method; - router_data.return_url = return_url; router_data.connector_meta_data = connector_meta_data; router_data.amount_captured = amount_captured; router_data.minor_amount_captured = minor_amount_captured; @@ -313,7 +311,6 @@ impl RouterDataConversion for Disputes payment_id: old_router_data.payment_id.clone(), attempt_id: old_router_data.attempt_id.clone(), payment_method: old_router_data.payment_method, - return_url: old_router_data.return_url.clone(), connector_meta_data: old_router_data.connector_meta_data.clone(), amount_captured: old_router_data.amount_captured, minor_amount_captured: old_router_data.minor_amount_captured, @@ -344,7 +341,6 @@ impl RouterDataConversion for Disputes payment_id, attempt_id, payment_method, - return_url, connector_meta_data, amount_captured, minor_amount_captured, @@ -360,7 +356,6 @@ impl RouterDataConversion for Disputes router_data.payment_id = payment_id; router_data.attempt_id = attempt_id; router_data.payment_method = payment_method; - router_data.return_url = return_url; router_data.connector_meta_data = connector_meta_data; router_data.amount_captured = amount_captured; router_data.minor_amount_captured = minor_amount_captured; @@ -384,7 +379,6 @@ impl RouterDataConversion for FrmFlowD attempt_id: old_router_data.attempt_id.clone(), payment_method: old_router_data.payment_method, connector_request_reference_id: old_router_data.connector_request_reference_id.clone(), - return_url: old_router_data.return_url.clone(), auth_type: old_router_data.auth_type, connector_wallets_details: old_router_data.connector_wallets_details.clone(), connector_meta_data: old_router_data.connector_meta_data.clone(), @@ -412,7 +406,6 @@ impl RouterDataConversion for FrmFlowD attempt_id, payment_method, connector_request_reference_id, - return_url, auth_type, connector_wallets_details, connector_meta_data, @@ -427,7 +420,6 @@ impl RouterDataConversion for FrmFlowD router_data.attempt_id = attempt_id; router_data.payment_method = payment_method; router_data.connector_request_reference_id = connector_request_reference_id; - router_data.return_url = return_url; router_data.auth_type = auth_type; router_data.connector_wallets_details = connector_wallets_details; router_data.connector_meta_data = connector_meta_data; @@ -449,7 +441,6 @@ impl RouterDataConversion for FilesFlo merchant_id: old_router_data.merchant_id.clone(), payment_id: old_router_data.payment_id.clone(), attempt_id: old_router_data.attempt_id.clone(), - return_url: old_router_data.return_url.clone(), connector_meta_data: old_router_data.connector_meta_data.clone(), connector_request_reference_id: old_router_data.connector_request_reference_id.clone(), }; @@ -472,7 +463,6 @@ impl RouterDataConversion for FilesFlo merchant_id, payment_id, attempt_id, - return_url, connector_meta_data, connector_request_reference_id, } = new_router_data.resource_common_data; @@ -481,7 +471,6 @@ impl RouterDataConversion for FilesFlo router_data.merchant_id = merchant_id; router_data.payment_id = payment_id; router_data.attempt_id = attempt_id; - router_data.return_url = return_url; router_data.connector_meta_data = connector_meta_data; router_data.connector_request_reference_id = connector_request_reference_id; @@ -591,7 +580,6 @@ impl RouterDataConversion for PayoutFl merchant_id: old_router_data.merchant_id.clone(), customer_id: old_router_data.customer_id.clone(), connector_customer: old_router_data.connector_customer.clone(), - return_url: old_router_data.return_url.clone(), address: old_router_data.address.clone(), connector_meta_data: old_router_data.connector_meta_data.clone(), connector_wallets_details: old_router_data.connector_wallets_details.clone(), @@ -618,7 +606,6 @@ impl RouterDataConversion for PayoutFl merchant_id, customer_id, connector_customer, - return_url, address, connector_meta_data, connector_wallets_details, @@ -631,7 +618,6 @@ impl RouterDataConversion for PayoutFl router_data.merchant_id = merchant_id; router_data.customer_id = customer_id; router_data.connector_customer = connector_customer; - router_data.return_url = return_url; router_data.address = address; router_data.connector_meta_data = connector_meta_data; router_data.connector_wallets_details = connector_wallets_details; @@ -686,3 +672,47 @@ impl RouterDataConversion Ok(router_data) } } + +impl RouterDataConversion for UasFlowData { + fn from_old_router_data( + old_router_data: &RouterData, + ) -> errors::CustomResult, errors::ConnectorError> + where + Self: Sized, + { + let resource_common_data = Self { + authenticate_by: old_router_data.connector.clone(), + source_authentication_id: old_router_data + .authentication_id + .clone() + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "source_authentication_id", + }) + .attach_printable("missing authentication id for uas")?, + }; + Ok(RouterDataV2 { + flow: std::marker::PhantomData, + resource_common_data, + connector_auth_type: old_router_data.connector_auth_type.clone(), + request: old_router_data.request.clone(), + response: old_router_data.response.clone(), + }) + } + + fn to_old_router_data( + new_router_data: RouterDataV2, + ) -> errors::CustomResult, errors::ConnectorError> + where + Self: Sized, + { + let Self { + authenticate_by, + source_authentication_id, + } = new_router_data.resource_common_data; + let mut router_data = + get_default_router_data("uas", new_router_data.request, new_router_data.response); + router_data.connector = authenticate_by; + router_data.authentication_id = Some(source_authentication_id); + Ok(router_data) + } +} diff --git a/crates/router/src/services/email/assets/welcome_to_community.html b/crates/router/src/services/email/assets/welcome_to_community.html new file mode 100644 index 000000000000..05f7fac1d55a --- /dev/null +++ b/crates/router/src/services/email/assets/welcome_to_community.html @@ -0,0 +1,306 @@ + + + + + + + Email Template + + + + + +
+ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+ Follow us on + + Twitter + + + LinkedIn + +
+
+
+ + + diff --git a/crates/router/src/services/email/types.rs b/crates/router/src/services/email/types.rs index 1c4ce79ac8cc..476cd1292f6a 100644 --- a/crates/router/src/services/email/types.rs +++ b/crates/router/src/services/email/types.rs @@ -1,9 +1,6 @@ use api_models::user::dashboard_metadata::ProdIntent; use common_enums::EntityType; -use common_utils::{ - errors::{self, CustomResult}, - pii, -}; +use common_utils::{errors::CustomResult, pii, types::theme::EmailThemeConfig}; use error_stack::ResultExt; use external_services::email::{EmailContents, EmailData, EmailError}; use masking::{ExposeInterface, Secret}; @@ -57,6 +54,7 @@ pub enum EmailBody { api_key_name: String, prefix: String, }, + WelcomeToCommunity, } pub mod html { @@ -145,6 +143,9 @@ Email : {user_email} prefix = prefix, expires_in = expires_in, ), + EmailBody::WelcomeToCommunity => { + include_str!("assets/welcome_to_community.html").to_string() + } } } } @@ -179,7 +180,7 @@ impl EmailToken { entity: Option, flow: domain::Origin, settings: &configs::Settings, - ) -> CustomResult { + ) -> UserResult { let expiration_duration = std::time::Duration::from_secs(consts::EMAIL_TOKEN_TIME_IN_SECS); let exp = jwt::generate_exp(expiration_duration)?.as_secs(); let token_payload = Self { @@ -191,8 +192,10 @@ impl EmailToken { jwt::generate_jwt(&token_payload, settings).await } - pub fn get_email(&self) -> CustomResult { + pub fn get_email(&self) -> UserResult { pii::Email::try_from(self.email.clone()) + .change_context(UserErrors::InternalServerError) + .and_then(domain::UserEmail::from_pii_email) } pub fn get_entity(&self) -> Option<&Entity> { @@ -209,12 +212,24 @@ pub fn get_link_with_token( token: impl std::fmt::Display, action: impl std::fmt::Display, auth_id: &Option, + theme_id: &Option, ) -> String { - let email_url = format!("{base_url}/user/{action}?token={token}"); + let mut email_url = format!("{base_url}/user/{action}?token={token}"); if let Some(auth_id) = auth_id { - format!("{email_url}&auth_id={auth_id}") + email_url = format!("{email_url}&auth_id={auth_id}"); + } + if let Some(theme_id) = theme_id { + email_url = format!("{email_url}&theme_id={theme_id}"); + } + + email_url +} + +pub fn get_base_url(state: &SessionState) -> &str { + if !state.conf.multitenancy.enabled { + &state.conf.user.base_url } else { - email_url + &state.tenant.user.control_center_url } } @@ -223,12 +238,14 @@ pub struct VerifyEmail { pub settings: std::sync::Arc, pub subject: &'static str, pub auth_id: Option, + pub theme_id: Option, + pub theme_config: EmailThemeConfig, } /// Currently only HTML is supported #[async_trait::async_trait] impl EmailData for VerifyEmail { - async fn get_email_data(&self) -> CustomResult { + async fn get_email_data(&self, base_url: &str) -> CustomResult { let token = EmailToken::new_token( self.recipient_email.clone(), None, @@ -239,10 +256,11 @@ impl EmailData for VerifyEmail { .change_context(EmailError::TokenGenerationFailure)?; let verify_email_link = get_link_with_token( - &self.settings.user.base_url, + base_url, token, "verify_email", &self.auth_id, + &self.theme_id, ); let body = html::get_html_body(EmailBody::Verify { @@ -263,11 +281,13 @@ pub struct ResetPassword { pub settings: std::sync::Arc, pub subject: &'static str, pub auth_id: Option, + pub theme_id: Option, + pub theme_config: EmailThemeConfig, } #[async_trait::async_trait] impl EmailData for ResetPassword { - async fn get_email_data(&self) -> CustomResult { + async fn get_email_data(&self, base_url: &str) -> CustomResult { let token = EmailToken::new_token( self.recipient_email.clone(), None, @@ -278,10 +298,11 @@ impl EmailData for ResetPassword { .change_context(EmailError::TokenGenerationFailure)?; let reset_password_link = get_link_with_token( - &self.settings.user.base_url, + base_url, token, "set_password", &self.auth_id, + &self.theme_id, ); let body = html::get_html_body(EmailBody::Reset { @@ -303,11 +324,13 @@ pub struct MagicLink { pub settings: std::sync::Arc, pub subject: &'static str, pub auth_id: Option, + pub theme_id: Option, + pub theme_config: EmailThemeConfig, } #[async_trait::async_trait] impl EmailData for MagicLink { - async fn get_email_data(&self) -> CustomResult { + async fn get_email_data(&self, base_url: &str) -> CustomResult { let token = EmailToken::new_token( self.recipient_email.clone(), None, @@ -318,10 +341,11 @@ impl EmailData for MagicLink { .change_context(EmailError::TokenGenerationFailure)?; let magic_link_login = get_link_with_token( - &self.settings.user.base_url, + base_url, token, "verify_email", &self.auth_id, + &self.theme_id, ); let body = html::get_html_body(EmailBody::MagicLink { @@ -344,11 +368,13 @@ pub struct InviteUser { pub subject: &'static str, pub entity: Entity, pub auth_id: Option, + pub theme_id: Option, + pub theme_config: EmailThemeConfig, } #[async_trait::async_trait] impl EmailData for InviteUser { - async fn get_email_data(&self) -> CustomResult { + async fn get_email_data(&self, base_url: &str) -> CustomResult { let token = EmailToken::new_token( self.recipient_email.clone(), Some(self.entity.clone()), @@ -359,10 +385,11 @@ impl EmailData for InviteUser { .change_context(EmailError::TokenGenerationFailure)?; let invite_user_link = get_link_with_token( - &self.settings.user.base_url, + base_url, token, "accept_invite_from_email", &self.auth_id, + &self.theme_id, ); let body = html::get_html_body(EmailBody::AcceptInviteFromEmail { link: invite_user_link, @@ -380,13 +407,14 @@ impl EmailData for InviteUser { pub struct ReconActivation { pub recipient_email: domain::UserEmail, pub user_name: domain::UserName, - pub settings: std::sync::Arc, pub subject: &'static str, + pub theme_id: Option, + pub theme_config: EmailThemeConfig, } #[async_trait::async_trait] impl EmailData for ReconActivation { - async fn get_email_data(&self) -> CustomResult { + async fn get_email_data(&self, _base_url: &str) -> CustomResult { let body = html::get_html_body(EmailBody::ReconActivation { user_name: self.user_name.clone().get_secret().expose(), }); @@ -408,16 +436,23 @@ pub struct BizEmailProd { pub business_website: String, pub settings: std::sync::Arc, pub subject: &'static str, + pub theme_id: Option, + pub theme_config: EmailThemeConfig, } impl BizEmailProd { - pub fn new(state: &SessionState, data: ProdIntent) -> UserResult { + pub fn new( + state: &SessionState, + data: ProdIntent, + theme_id: Option, + theme_config: EmailThemeConfig, + ) -> UserResult { Ok(Self { recipient_email: domain::UserEmail::from_pii_email( state.conf.email.prod_intent_recipient_email.clone(), )?, settings: state.conf.clone(), - subject: "New Prod Intent", + subject: consts::user::EMAIL_SUBJECT_NEW_PROD_INTENT, user_name: data.poc_name.unwrap_or_default().into(), poc_email: data.poc_email.unwrap_or_default().into(), legal_business_name: data.legal_business_name.unwrap_or_default(), @@ -426,13 +461,15 @@ impl BizEmailProd { .unwrap_or(common_enums::CountryAlpha2::AD) .to_string(), business_website: data.business_website.unwrap_or_default(), + theme_id, + theme_config, }) } } #[async_trait::async_trait] impl EmailData for BizEmailProd { - async fn get_email_data(&self) -> CustomResult { + async fn get_email_data(&self, _base_url: &str) -> CustomResult { let body = html::get_html_body(EmailBody::BizEmailProd { user_name: self.user_name.clone().expose(), poc_email: self.poc_email.clone().expose(), @@ -455,13 +492,14 @@ pub struct ProFeatureRequest { pub merchant_id: common_utils::id_type::MerchantId, pub user_name: domain::UserName, pub user_email: domain::UserEmail, - pub settings: std::sync::Arc, pub subject: String, + pub theme_id: Option, + pub theme_config: EmailThemeConfig, } #[async_trait::async_trait] impl EmailData for ProFeatureRequest { - async fn get_email_data(&self) -> CustomResult { + async fn get_email_data(&self, _base_url: &str) -> CustomResult { let recipient = self.recipient_email.clone().into_inner(); let body = html::get_html_body(EmailBody::ProFeatureRequest { @@ -485,11 +523,13 @@ pub struct ApiKeyExpiryReminder { pub expires_in: u8, pub api_key_name: String, pub prefix: String, + pub theme_id: Option, + pub theme_config: EmailThemeConfig, } #[async_trait::async_trait] impl EmailData for ApiKeyExpiryReminder { - async fn get_email_data(&self) -> CustomResult { + async fn get_email_data(&self, _base_url: &str) -> CustomResult { let recipient = self.recipient_email.clone().into_inner(); let body = html::get_html_body(EmailBody::ApiKeyExpiryReminder { @@ -505,3 +545,21 @@ impl EmailData for ApiKeyExpiryReminder { }) } } + +pub struct WelcomeToCommunity { + pub recipient_email: domain::UserEmail, + pub subject: &'static str, +} + +#[async_trait::async_trait] +impl EmailData for WelcomeToCommunity { + async fn get_email_data(&self, _base_url: &str) -> CustomResult { + let body = html::get_html_body(EmailBody::WelcomeToCommunity); + + Ok(EmailContents { + subject: self.subject.to_string(), + body: external_services::email::IntermediateString::new(body), + recipient: self.recipient_email.clone().into_inner(), + }) + } +} diff --git a/crates/router/src/services/kafka.rs b/crates/router/src/services/kafka.rs index 4fd692e1166f..91968510a6b6 100644 --- a/crates/router/src/services/kafka.rs +++ b/crates/router/src/services/kafka.rs @@ -94,7 +94,7 @@ impl<'a, T: KafkaMessage> KafkaEvent<'a, T> { } } -impl<'a, T: KafkaMessage> KafkaMessage for KafkaEvent<'a, T> { +impl KafkaMessage for KafkaEvent<'_, T> { fn key(&self) -> String { self.event.key() } @@ -130,7 +130,7 @@ impl<'a, T: KafkaMessage> KafkaConsolidatedEvent<'a, T> { } } -impl<'a, T: KafkaMessage> KafkaMessage for KafkaConsolidatedEvent<'a, T> { +impl KafkaMessage for KafkaConsolidatedEvent<'_, T> { fn key(&self) -> String { self.log.event.key() } @@ -582,6 +582,21 @@ impl KafkaProducer { .attach_printable_lazy(|| format!("Failed to add consolidated dispute event {dispute:?}")) } + pub async fn log_dispute_delete( + &self, + delete_old_dispute: &Dispute, + tenant_id: TenantID, + ) -> MQResult<()> { + self.log_event(&KafkaEvent::old( + &KafkaDispute::from_storage(delete_old_dispute), + tenant_id.clone(), + self.ckh_database_name.clone(), + )) + .attach_printable_lazy(|| { + format!("Failed to add negative dispute event {delete_old_dispute:?}") + }) + } + #[cfg(feature = "payouts")] pub async fn log_payout( &self, diff --git a/crates/router/src/services/kafka/authentication.rs b/crates/router/src/services/kafka/authentication.rs index 67e488d6c8fb..5cf2d066ee2e 100644 --- a/crates/router/src/services/kafka/authentication.rs +++ b/crates/router/src/services/kafka/authentication.rs @@ -86,7 +86,7 @@ impl<'a> KafkaAuthentication<'a> { } } -impl<'a> super::KafkaMessage for KafkaAuthentication<'a> { +impl super::KafkaMessage for KafkaAuthentication<'_> { fn key(&self) -> String { format!( "{}_{}", diff --git a/crates/router/src/services/kafka/authentication_event.rs b/crates/router/src/services/kafka/authentication_event.rs index 7c8b77a38486..4169ff7b42a2 100644 --- a/crates/router/src/services/kafka/authentication_event.rs +++ b/crates/router/src/services/kafka/authentication_event.rs @@ -87,7 +87,7 @@ impl<'a> KafkaAuthenticationEvent<'a> { } } -impl<'a> super::KafkaMessage for KafkaAuthenticationEvent<'a> { +impl super::KafkaMessage for KafkaAuthenticationEvent<'_> { fn key(&self) -> String { format!( "{}_{}", diff --git a/crates/router/src/services/kafka/dispute.rs b/crates/router/src/services/kafka/dispute.rs index 2afb41cc8673..4414af494f2f 100644 --- a/crates/router/src/services/kafka/dispute.rs +++ b/crates/router/src/services/kafka/dispute.rs @@ -1,4 +1,4 @@ -use common_utils::id_type; +use common_utils::{ext_traits::StringExt, id_type}; use diesel_models::enums as storage_enums; use masking::Secret; use time::OffsetDateTime; @@ -9,7 +9,7 @@ use crate::types::storage::dispute::Dispute; pub struct KafkaDispute<'a> { pub dispute_id: &'a String, pub dispute_amount: i64, - pub currency: &'a String, + pub currency: storage_enums::Currency, pub dispute_stage: &'a storage_enums::DisputeStage, pub dispute_status: &'a storage_enums::DisputeStatus, pub payment_id: &'a id_type::PaymentId, @@ -41,7 +41,13 @@ impl<'a> KafkaDispute<'a> { Self { dispute_id: &dispute.dispute_id, dispute_amount: dispute.amount.parse::().unwrap_or_default(), - currency: &dispute.currency, + currency: dispute.dispute_currency.unwrap_or( + dispute + .currency + .to_uppercase() + .parse_enum("Currency") + .unwrap_or_default(), + ), dispute_stage: &dispute.dispute_stage, dispute_status: &dispute.dispute_status, payment_id: &dispute.payment_id, @@ -65,7 +71,7 @@ impl<'a> KafkaDispute<'a> { } } -impl<'a> super::KafkaMessage for KafkaDispute<'a> { +impl super::KafkaMessage for KafkaDispute<'_> { fn key(&self) -> String { format!( "{}_{}_{}", diff --git a/crates/router/src/services/kafka/dispute_event.rs b/crates/router/src/services/kafka/dispute_event.rs index 057a060decda..45e7ff2c7981 100644 --- a/crates/router/src/services/kafka/dispute_event.rs +++ b/crates/router/src/services/kafka/dispute_event.rs @@ -1,3 +1,4 @@ +use common_utils::ext_traits::StringExt; use diesel_models::enums as storage_enums; use masking::Secret; use time::OffsetDateTime; @@ -9,7 +10,7 @@ use crate::types::storage::dispute::Dispute; pub struct KafkaDisputeEvent<'a> { pub dispute_id: &'a String, pub dispute_amount: i64, - pub currency: &'a String, + pub currency: storage_enums::Currency, pub dispute_stage: &'a storage_enums::DisputeStage, pub dispute_status: &'a storage_enums::DisputeStatus, pub payment_id: &'a common_utils::id_type::PaymentId, @@ -19,15 +20,15 @@ pub struct KafkaDisputeEvent<'a> { pub connector_dispute_id: &'a String, pub connector_reason: Option<&'a String>, pub connector_reason_code: Option<&'a String>, - #[serde(default, with = "time::serde::timestamp::milliseconds::option")] + #[serde(default, with = "time::serde::timestamp::nanoseconds::option")] pub challenge_required_by: Option, - #[serde(default, with = "time::serde::timestamp::milliseconds::option")] + #[serde(default, with = "time::serde::timestamp::nanoseconds::option")] pub connector_created_at: Option, - #[serde(default, with = "time::serde::timestamp::milliseconds::option")] + #[serde(default, with = "time::serde::timestamp::nanoseconds::option")] pub connector_updated_at: Option, - #[serde(default, with = "time::serde::timestamp::milliseconds")] + #[serde(default, with = "time::serde::timestamp::nanoseconds")] pub created_at: OffsetDateTime, - #[serde(default, with = "time::serde::timestamp::milliseconds")] + #[serde(default, with = "time::serde::timestamp::nanoseconds")] pub modified_at: OffsetDateTime, pub connector: &'a String, pub evidence: &'a Secret, @@ -41,7 +42,13 @@ impl<'a> KafkaDisputeEvent<'a> { Self { dispute_id: &dispute.dispute_id, dispute_amount: dispute.amount.parse::().unwrap_or_default(), - currency: &dispute.currency, + currency: dispute.dispute_currency.unwrap_or( + dispute + .currency + .to_uppercase() + .parse_enum("Currency") + .unwrap_or_default(), + ), dispute_stage: &dispute.dispute_stage, dispute_status: &dispute.dispute_status, payment_id: &dispute.payment_id, @@ -65,7 +72,7 @@ impl<'a> KafkaDisputeEvent<'a> { } } -impl<'a> super::KafkaMessage for KafkaDisputeEvent<'a> { +impl super::KafkaMessage for KafkaDisputeEvent<'_> { fn key(&self) -> String { format!( "{}_{}_{}", diff --git a/crates/router/src/services/kafka/fraud_check.rs b/crates/router/src/services/kafka/fraud_check.rs index 26fa9e6b4e26..3010f85753b9 100644 --- a/crates/router/src/services/kafka/fraud_check.rs +++ b/crates/router/src/services/kafka/fraud_check.rs @@ -53,7 +53,7 @@ impl<'a> KafkaFraudCheck<'a> { } } -impl<'a> super::KafkaMessage for KafkaFraudCheck<'a> { +impl super::KafkaMessage for KafkaFraudCheck<'_> { fn key(&self) -> String { format!( "{}_{}_{}_{}", diff --git a/crates/router/src/services/kafka/fraud_check_event.rs b/crates/router/src/services/kafka/fraud_check_event.rs index f35748cb1c8c..4fd711744188 100644 --- a/crates/router/src/services/kafka/fraud_check_event.rs +++ b/crates/router/src/services/kafka/fraud_check_event.rs @@ -52,7 +52,7 @@ impl<'a> KafkaFraudCheckEvent<'a> { } } -impl<'a> super::KafkaMessage for KafkaFraudCheckEvent<'a> { +impl super::KafkaMessage for KafkaFraudCheckEvent<'_> { fn key(&self) -> String { format!( "{}_{}_{}_{}", diff --git a/crates/router/src/services/kafka/payment_attempt.rs b/crates/router/src/services/kafka/payment_attempt.rs index 76894928434a..adfff7450bc0 100644 --- a/crates/router/src/services/kafka/payment_attempt.rs +++ b/crates/router/src/services/kafka/payment_attempt.rs @@ -67,14 +67,14 @@ impl<'a> KafkaPaymentAttempt<'a> { merchant_id: &attempt.merchant_id, attempt_id: &attempt.attempt_id, status: attempt.status, - amount: attempt.amount, + amount: attempt.net_amount.get_order_amount(), currency: attempt.currency, save_to_locker: attempt.save_to_locker, connector: attempt.connector.as_ref(), error_message: attempt.error_message.as_ref(), offer_amount: attempt.offer_amount, - surcharge_amount: attempt.surcharge_amount, - tax_amount: attempt.tax_amount, + surcharge_amount: attempt.net_amount.get_surcharge_amount(), + tax_amount: attempt.net_amount.get_tax_on_surcharge(), payment_method_id: attempt.payment_method_id.as_ref(), payment_method: attempt.payment_method, connector_transaction_id: attempt.connector_transaction_id.as_ref(), @@ -98,7 +98,7 @@ impl<'a> KafkaPaymentAttempt<'a> { multiple_capture_count: attempt.multiple_capture_count, amount_capturable: attempt.amount_capturable, merchant_connector_id: attempt.merchant_connector_id.as_ref(), - net_amount: attempt.net_amount, + net_amount: attempt.net_amount.get_total_amount(), unified_code: attempt.unified_code.as_ref(), unified_message: attempt.unified_message.as_ref(), mandate_data: attempt.mandate_data.as_ref(), @@ -180,7 +180,7 @@ impl<'a> KafkaPaymentAttempt<'a> { } } -impl<'a> super::KafkaMessage for KafkaPaymentAttempt<'a> { +impl super::KafkaMessage for KafkaPaymentAttempt<'_> { fn key(&self) -> String { format!( "{}_{}_{}", diff --git a/crates/router/src/services/kafka/payment_attempt_event.rs b/crates/router/src/services/kafka/payment_attempt_event.rs index b4d6cd8835ad..ec67dc759533 100644 --- a/crates/router/src/services/kafka/payment_attempt_event.rs +++ b/crates/router/src/services/kafka/payment_attempt_event.rs @@ -25,15 +25,15 @@ pub struct KafkaPaymentAttemptEvent<'a> { pub payment_method: Option, pub connector_transaction_id: Option<&'a String>, pub capture_method: Option, - #[serde(default, with = "time::serde::timestamp::milliseconds::option")] + #[serde(default, with = "time::serde::timestamp::nanoseconds::option")] pub capture_on: Option, pub confirm: bool, pub authentication_type: Option, - #[serde(with = "time::serde::timestamp::milliseconds")] + #[serde(with = "time::serde::timestamp::nanoseconds")] pub created_at: OffsetDateTime, - #[serde(with = "time::serde::timestamp::milliseconds")] + #[serde(with = "time::serde::timestamp::nanoseconds")] pub modified_at: OffsetDateTime, - #[serde(default, with = "time::serde::timestamp::milliseconds::option")] + #[serde(default, with = "time::serde::timestamp::nanoseconds::option")] pub last_synced: Option, pub cancellation_reason: Option<&'a String>, pub amount_to_capture: Option, @@ -68,14 +68,14 @@ impl<'a> KafkaPaymentAttemptEvent<'a> { merchant_id: &attempt.merchant_id, attempt_id: &attempt.attempt_id, status: attempt.status, - amount: attempt.amount, + amount: attempt.net_amount.get_order_amount(), currency: attempt.currency, save_to_locker: attempt.save_to_locker, connector: attempt.connector.as_ref(), error_message: attempt.error_message.as_ref(), offer_amount: attempt.offer_amount, - surcharge_amount: attempt.surcharge_amount, - tax_amount: attempt.tax_amount, + surcharge_amount: attempt.net_amount.get_surcharge_amount(), + tax_amount: attempt.net_amount.get_tax_on_surcharge(), payment_method_id: attempt.payment_method_id.as_ref(), payment_method: attempt.payment_method, connector_transaction_id: attempt.connector_transaction_id.as_ref(), @@ -99,7 +99,7 @@ impl<'a> KafkaPaymentAttemptEvent<'a> { multiple_capture_count: attempt.multiple_capture_count, amount_capturable: attempt.amount_capturable, merchant_connector_id: attempt.merchant_connector_id.as_ref(), - net_amount: attempt.net_amount, + net_amount: attempt.net_amount.get_total_amount(), unified_code: attempt.unified_code.as_ref(), unified_message: attempt.unified_message.as_ref(), mandate_data: attempt.mandate_data.as_ref(), @@ -181,7 +181,7 @@ impl<'a> KafkaPaymentAttemptEvent<'a> { } } -impl<'a> super::KafkaMessage for KafkaPaymentAttemptEvent<'a> { +impl super::KafkaMessage for KafkaPaymentAttemptEvent<'_> { fn key(&self) -> String { format!( "{}_{}_{}", diff --git a/crates/router/src/services/kafka/payment_intent.rs b/crates/router/src/services/kafka/payment_intent.rs index 82e33de8c609..378bddf193ba 100644 --- a/crates/router/src/services/kafka/payment_intent.rs +++ b/crates/router/src/services/kafka/payment_intent.rs @@ -180,7 +180,7 @@ impl KafkaPaymentIntent<'_> { } } -impl<'a> super::KafkaMessage for KafkaPaymentIntent<'a> { +impl super::KafkaMessage for KafkaPaymentIntent<'_> { fn key(&self) -> String { format!( "{}_{}", diff --git a/crates/router/src/services/kafka/payment_intent_event.rs b/crates/router/src/services/kafka/payment_intent_event.rs index 5657846ca4c7..195b8679d318 100644 --- a/crates/router/src/services/kafka/payment_intent_event.rs +++ b/crates/router/src/services/kafka/payment_intent_event.rs @@ -22,11 +22,11 @@ pub struct KafkaPaymentIntentEvent<'a> { pub connector_id: Option<&'a String>, pub statement_descriptor_name: Option<&'a String>, pub statement_descriptor_suffix: Option<&'a String>, - #[serde(with = "time::serde::timestamp::milliseconds")] + #[serde(with = "time::serde::timestamp::nanoseconds")] pub created_at: OffsetDateTime, - #[serde(with = "time::serde::timestamp::milliseconds")] + #[serde(with = "time::serde::timestamp::nanoseconds")] pub modified_at: OffsetDateTime, - #[serde(default, with = "time::serde::timestamp::milliseconds::option")] + #[serde(default, with = "time::serde::timestamp::nanoseconds::option")] pub last_synced: Option, pub setup_future_usage: Option, pub off_session: Option, @@ -60,11 +60,11 @@ pub struct KafkaPaymentIntentEvent<'a> { pub return_url: Option<&'a String>, pub metadata: Option, pub statement_descriptor: Option<&'a String>, - #[serde(with = "time::serde::timestamp::milliseconds")] + #[serde(with = "time::serde::timestamp::nanoseconds")] pub created_at: OffsetDateTime, - #[serde(with = "time::serde::timestamp::milliseconds")] + #[serde(with = "time::serde::timestamp::nanoseconds")] pub modified_at: OffsetDateTime, - #[serde(default, with = "time::serde::timestamp::milliseconds::option")] + #[serde(default, with = "time::serde::timestamp::nanoseconds::option")] pub last_synced: Option, pub setup_future_usage: Option, pub off_session: Option, @@ -182,7 +182,7 @@ impl<'a> KafkaPaymentIntentEvent<'a> { } } -impl<'a> super::KafkaMessage for KafkaPaymentIntentEvent<'a> { +impl super::KafkaMessage for KafkaPaymentIntentEvent<'_> { fn key(&self) -> String { format!( "{}_{}", diff --git a/crates/router/src/services/kafka/payout.rs b/crates/router/src/services/kafka/payout.rs index 7a6254bd527d..babf7c1c5682 100644 --- a/crates/router/src/services/kafka/payout.rs +++ b/crates/router/src/services/kafka/payout.rs @@ -77,7 +77,7 @@ impl<'a> KafkaPayout<'a> { } } -impl<'a> super::KafkaMessage for KafkaPayout<'a> { +impl super::KafkaMessage for KafkaPayout<'_> { fn key(&self) -> String { format!( "{}_{}", diff --git a/crates/router/src/services/kafka/refund.rs b/crates/router/src/services/kafka/refund.rs index c4fb4a43dd08..a7e37930cf2a 100644 --- a/crates/router/src/services/kafka/refund.rs +++ b/crates/router/src/services/kafka/refund.rs @@ -1,4 +1,7 @@ -use common_utils::{id_type, types::MinorUnit}; +use common_utils::{ + id_type, + types::{ConnectorTransactionIdTrait, MinorUnit}, +}; use diesel_models::{enums as storage_enums, refund::Refund}; use time::OffsetDateTime; @@ -39,9 +42,9 @@ impl<'a> KafkaRefund<'a> { refund_id: &refund.refund_id, payment_id: &refund.payment_id, merchant_id: &refund.merchant_id, - connector_transaction_id: &refund.connector_transaction_id, + connector_transaction_id: refund.get_connector_transaction_id(), connector: &refund.connector, - connector_refund_id: refund.connector_refund_id.as_ref(), + connector_refund_id: refund.get_optional_connector_refund_id(), external_reference_id: refund.external_reference_id.as_ref(), refund_type: &refund.refund_type, total_amount: &refund.total_amount, @@ -63,7 +66,7 @@ impl<'a> KafkaRefund<'a> { } } -impl<'a> super::KafkaMessage for KafkaRefund<'a> { +impl super::KafkaMessage for KafkaRefund<'_> { fn key(&self) -> String { format!( "{}_{}_{}_{}", diff --git a/crates/router/src/services/kafka/refund_event.rs b/crates/router/src/services/kafka/refund_event.rs index f2150020c974..b9b3db17b588 100644 --- a/crates/router/src/services/kafka/refund_event.rs +++ b/crates/router/src/services/kafka/refund_event.rs @@ -1,4 +1,7 @@ -use common_utils::{id_type, types::MinorUnit}; +use common_utils::{ + id_type, + types::{ConnectorTransactionIdTrait, MinorUnit}, +}; use diesel_models::{enums as storage_enums, refund::Refund}; use time::OffsetDateTime; @@ -21,9 +24,9 @@ pub struct KafkaRefundEvent<'a> { pub sent_to_gateway: &'a bool, pub refund_error_message: Option<&'a String>, pub refund_arn: Option<&'a String>, - #[serde(default, with = "time::serde::timestamp::milliseconds")] + #[serde(default, with = "time::serde::timestamp::nanoseconds")] pub created_at: OffsetDateTime, - #[serde(default, with = "time::serde::timestamp::milliseconds")] + #[serde(default, with = "time::serde::timestamp::nanoseconds")] pub modified_at: OffsetDateTime, pub description: Option<&'a String>, pub attempt_id: &'a String, @@ -40,9 +43,9 @@ impl<'a> KafkaRefundEvent<'a> { refund_id: &refund.refund_id, payment_id: &refund.payment_id, merchant_id: &refund.merchant_id, - connector_transaction_id: &refund.connector_transaction_id, + connector_transaction_id: refund.get_connector_transaction_id(), connector: &refund.connector, - connector_refund_id: refund.connector_refund_id.as_ref(), + connector_refund_id: refund.get_optional_connector_refund_id(), external_reference_id: refund.external_reference_id.as_ref(), refund_type: &refund.refund_type, total_amount: &refund.total_amount, @@ -64,7 +67,7 @@ impl<'a> KafkaRefundEvent<'a> { } } -impl<'a> super::KafkaMessage for KafkaRefundEvent<'a> { +impl super::KafkaMessage for KafkaRefundEvent<'_> { fn key(&self) -> String { format!( "{}_{}_{}_{}", diff --git a/crates/router/src/services/logger.rs b/crates/router/src/services/logger.rs index 7dfd7abb8441..e88d4072f906 100644 --- a/crates/router/src/services/logger.rs +++ b/crates/router/src/services/logger.rs @@ -1,5 +1,3 @@ -//! //! Logger of the system. -//! pub use crate::logger::*; diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index 548d216d6e8f..f9c9e6edb26e 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -33,7 +33,8 @@ use hyperswitch_domain_models::router_flow_types::{ payments::{ Approve, Authorize, AuthorizeSessionToken, Balance, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, IncrementalAuthorization, InitPayment, PSync, - PostProcessing, PreProcessing, Reject, SdkSessionUpdate, Session, SetupMandate, Void, + PostProcessing, PostSessionTokens, PreProcessing, Reject, SdkSessionUpdate, Session, + SetupMandate, Void, }, refunds::{Execute, RSync}, webhooks::VerifyWebhookSource, @@ -47,10 +48,14 @@ pub use hyperswitch_domain_models::{ }, router_data_v2::{ AccessTokenFlowData, DisputesFlowData, ExternalAuthenticationFlowData, FilesFlowData, - MandateRevokeFlowData, PaymentFlowData, RefundFlowData, RouterDataV2, + MandateRevokeFlowData, PaymentFlowData, RefundFlowData, RouterDataV2, UasFlowData, WebhookSourceVerifyData, }, router_request_types::{ + unified_authentication_service::{ + UasAuthenticationResponseData, UasPostAuthenticationRequestData, + UasPreAuthenticationRequestData, + }, AcceptDisputeRequestData, AccessTokenRequestData, AuthorizeSessionTokenData, BrowserInformation, ChargeRefunds, ChargeRefundsOptions, CompleteAuthorizeData, CompleteAuthorizeRedirectResponse, ConnectorCustomerData, DefendDisputeRequestData, @@ -58,10 +63,11 @@ pub use hyperswitch_domain_models::{ MultipleCaptureRequestData, PaymentMethodTokenizationData, PaymentsApproveData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsIncrementalAuthorizationData, PaymentsPostProcessingData, - PaymentsPreProcessingData, PaymentsRejectData, PaymentsSessionData, PaymentsSyncData, - PaymentsTaxCalculationData, RefundsData, ResponseId, RetrieveFileRequestData, - SdkPaymentsSessionUpdateData, SetupMandateRequestData, SubmitEvidenceRequestData, - SyncRequestType, UploadFileRequestData, VerifyWebhookSourceRequestData, + PaymentsPostSessionTokensData, PaymentsPreProcessingData, PaymentsRejectData, + PaymentsSessionData, PaymentsSyncData, PaymentsTaxCalculationData, RefundsData, ResponseId, + RetrieveFileRequestData, SdkPaymentsSessionUpdateData, SetupMandateRequestData, + SplitRefundsRequest, SubmitEvidenceRequestData, SyncRequestType, UploadFileRequestData, + VerifyWebhookSourceRequestData, }, router_response_types::{ AcceptDisputeResponse, CaptureSyncResponse, DefendDisputeResponse, MandateReference, @@ -80,10 +86,10 @@ pub use hyperswitch_interfaces::types::{ AcceptDisputeType, ConnectorCustomerType, DefendDisputeType, IncrementalAuthorizationType, MandateRevokeType, PaymentsAuthorizeType, PaymentsBalanceType, PaymentsCaptureType, PaymentsCompleteAuthorizeType, PaymentsInitType, PaymentsPostProcessingType, - PaymentsPreAuthorizeType, PaymentsPreProcessingType, PaymentsSessionType, PaymentsSyncType, - PaymentsVoidType, RefreshTokenType, RefundExecuteType, RefundSyncType, Response, - RetrieveFileType, SetupMandateType, SubmitEvidenceType, TokenizationType, UploadFileType, - VerifyWebhookSourceType, + PaymentsPostSessionTokensType, PaymentsPreAuthorizeType, PaymentsPreProcessingType, + PaymentsSessionType, PaymentsSyncType, PaymentsVoidType, RefreshTokenType, RefundExecuteType, + RefundSyncType, Response, RetrieveFileType, SdkSessionUpdateType, SetupMandateType, + SubmitEvidenceType, TokenizationType, UploadFileType, VerifyWebhookSourceType, }; #[cfg(feature = "payouts")] pub use hyperswitch_interfaces::types::{ @@ -134,6 +140,9 @@ pub type PaymentsTaxCalculationRouterData = pub type SdkSessionUpdateRouterData = RouterData; +pub type PaymentsPostSessionTokensRouterData = + RouterData; + pub type PaymentsCancelRouterData = RouterData; pub type PaymentsRejectRouterData = RouterData; pub type PaymentsApproveRouterData = RouterData; @@ -163,6 +172,8 @@ pub type PaymentsSessionResponseRouterData = ResponseRouterData; pub type PaymentsInitResponseRouterData = ResponseRouterData; +pub type SdkSessionUpdateResponseRouterData = + ResponseRouterData; pub type PaymentsCaptureResponseRouterData = ResponseRouterData; pub type PaymentsPreprocessingResponseRouterData = @@ -251,16 +262,18 @@ pub trait Capturable { } } +#[cfg(feature = "v1")] impl Capturable for PaymentsAuthorizeData { - fn get_captured_amount(&self, _payment_data: &PaymentData) -> Option + fn get_captured_amount(&self, payment_data: &PaymentData) -> Option where F: Clone, { - let final_amount = self - .surcharge_details - .as_ref() - .map(|surcharge_details| surcharge_details.final_amount.get_amount_as_i64()); - final_amount.or(Some(self.amount)) + Some( + payment_data + .payment_attempt + .get_total_amount() + .get_amount_as_i64(), + ) } fn get_amount_capturable( @@ -273,7 +286,7 @@ impl Capturable for PaymentsAuthorizeData { { match payment_data.get_capture_method().unwrap_or_default() { - common_enums::CaptureMethod::Automatic => { + common_enums::CaptureMethod::Automatic|common_enums::CaptureMethod::SequentialAutomatic => { let intent_status = common_enums::IntentStatus::foreign_from(attempt_status); match intent_status { common_enums::IntentStatus::Succeeded @@ -298,6 +311,7 @@ impl Capturable for PaymentsAuthorizeData { } } +#[cfg(feature = "v1")] impl Capturable for PaymentsCaptureData { fn get_captured_amount(&self, _payment_data: &PaymentData) -> Option where @@ -330,12 +344,18 @@ impl Capturable for PaymentsCaptureData { } } +#[cfg(feature = "v1")] impl Capturable for CompleteAuthorizeData { - fn get_captured_amount(&self, _payment_data: &PaymentData) -> Option + fn get_captured_amount(&self, payment_data: &PaymentData) -> Option where F: Clone, { - Some(self.amount) + Some( + payment_data + .payment_attempt + .get_total_amount() + .get_amount_as_i64(), + ) } fn get_amount_capturable( &self, @@ -349,7 +369,7 @@ impl Capturable for CompleteAuthorizeData { .get_capture_method() .unwrap_or_default() { - common_enums::CaptureMethod::Automatic => { + common_enums::CaptureMethod::Automatic | common_enums::CaptureMethod::SequentialAutomatic => { let intent_status = common_enums::IntentStatus::foreign_from(attempt_status); match intent_status { common_enums::IntentStatus::Succeeded| @@ -373,9 +393,11 @@ impl Capturable for CompleteAuthorizeData { } } } + impl Capturable for SetupMandateRequestData {} impl Capturable for PaymentsTaxCalculationData {} impl Capturable for SdkPaymentsSessionUpdateData {} +impl Capturable for PaymentsPostSessionTokensData {} impl Capturable for PaymentsCancelData { fn get_captured_amount(&self, payment_data: &PaymentData) -> Option where @@ -427,6 +449,7 @@ impl Capturable for PaymentsIncrementalAuthorizationData { } } impl Capturable for PaymentsSyncData { + #[cfg(feature = "v1")] fn get_captured_amount(&self, payment_data: &PaymentData) -> Option where F: Clone, @@ -437,6 +460,21 @@ impl Capturable for PaymentsSyncData { .or_else(|| Some(payment_data.payment_attempt.get_total_amount())) .map(|amt| amt.get_amount_as_i64()) } + + #[cfg(feature = "v2")] + fn get_captured_amount(&self, payment_data: &PaymentData) -> Option + where + F: Clone, + { + // TODO: add a getter for this + payment_data + .payment_attempt + .amount_details + .get_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, _payment_data: &PaymentData, @@ -490,12 +528,20 @@ impl Default for PollConfig { } } +#[cfg(feature = "v1")] #[derive(Clone, Debug)] pub struct RedirectPaymentFlowResponse { pub payments_response: api_models::payments::PaymentsResponse, pub business_profile: domain::Profile, } +#[cfg(feature = "v2")] +#[derive(Clone, Debug)] +pub struct RedirectPaymentFlowResponse { + pub payment_data: D, + pub profile: domain::Profile, +} + #[derive(Clone, Debug)] pub struct AuthenticatePaymentFlowResponse { pub payments_response: api_models::payments::PaymentsResponse, @@ -827,6 +873,7 @@ impl ForeignFrom<&SetupMandateRouterData> for PaymentsAuthorizeData { email: data.request.email.clone(), customer_name: data.request.customer_name.clone(), amount: 0, + order_tax_amount: Some(MinorUnit::zero()), minor_amount: MinorUnit::new(0), statement_descriptor: None, capture_method: None, @@ -846,9 +893,11 @@ impl ForeignFrom<&SetupMandateRouterData> for PaymentsAuthorizeData { metadata: None, authentication_data: None, customer_acceptance: data.request.customer_acceptance.clone(), - charges: None, // TODO: allow charges on mandates? + split_payments: None, // TODO: allow charges on mandates? merchant_order_reference_id: None, integrity_object: None, + additional_payment_method_data: None, + shipping_cost: data.request.shipping_cost, } } } @@ -869,7 +918,6 @@ impl ForeignFrom<(&RouterData, T2) payment_method: data.payment_method, connector_auth_type: data.connector_auth_type.clone(), description: data.description.clone(), - return_url: data.return_url.clone(), address: data.address.clone(), auth_type: data.auth_type, connector_meta_data: data.connector_meta_data.clone(), @@ -905,6 +953,11 @@ impl ForeignFrom<(&RouterData, T2) integrity_check: Ok(()), additional_merchant_data: data.additional_merchant_data.clone(), header_payload: data.header_payload.clone(), + connector_mandate_request_reference_id: data + .connector_mandate_request_reference_id + .clone(), + authentication_id: data.authentication_id.clone(), + psd2_sca_exemption_type: data.psd2_sca_exemption_type, } } } @@ -934,7 +987,6 @@ impl payment_method: data.payment_method, connector_auth_type: data.connector_auth_type.clone(), description: data.description.clone(), - return_url: data.return_url.clone(), address: data.address.clone(), auth_type: data.auth_type, connector_meta_data: data.connector_meta_data.clone(), @@ -967,8 +1019,11 @@ impl dispute_id: None, connector_response: data.connector_response.clone(), integrity_check: Ok(()), - additional_merchant_data: data.additional_merchant_data.clone(), header_payload: data.header_payload.clone(), + authentication_id: None, + psd2_sca_exemption_type: None, + additional_merchant_data: data.additional_merchant_data.clone(), + connector_mandate_request_reference_id: None, } } } diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 5fb945daf856..b0c3d2d21df8 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -50,6 +50,7 @@ pub use hyperswitch_interfaces::api::{ ConnectorMandateRevoke, ConnectorMandateRevokeV2, ConnectorVerifyWebhookSource, ConnectorVerifyWebhookSourceV2, CurrencyUnit, }; +use hyperswitch_interfaces::api::{UnifiedAuthenticationService, UnifiedAuthenticationServiceV2}; #[cfg(feature = "frm")] pub use self::fraud_check::*; @@ -76,6 +77,8 @@ pub enum ConnectorCallType { PreDetermined(ConnectorData), Retryable(Vec), SessionMultiple(Vec), + #[cfg(feature = "v2")] + Skip, } pub trait ConnectorTransactionId: ConnectorCommon + Sync { @@ -88,9 +91,6 @@ pub trait ConnectorTransactionId: ConnectorCommon + Sync { .map(ToString::to_string)) } } - -pub trait Router {} - pub trait Connector: Send + Refund @@ -107,6 +107,7 @@ pub trait Connector: + ConnectorMandateRevoke + ExternalAuthentication + TaxCalculation + + UnifiedAuthenticationService { } @@ -125,7 +126,8 @@ impl< + FraudCheck + ConnectorMandateRevoke + ExternalAuthentication - + TaxCalculation, + + TaxCalculation + + UnifiedAuthenticationService, > Connector for T { } @@ -145,6 +147,7 @@ pub trait ConnectorV2: + FraudCheckV2 + ConnectorMandateRevokeV2 + ExternalAuthenticationV2 + + UnifiedAuthenticationServiceV2 { } impl< @@ -161,7 +164,8 @@ impl< + ConnectorVerifyWebhookSourceV2 + FraudCheckV2 + ConnectorMandateRevokeV2 - + ExternalAuthenticationV2, + + ExternalAuthenticationV2 + + UnifiedAuthenticationServiceV2, > ConnectorV2 for T { } @@ -177,6 +181,7 @@ pub enum GetToken { SamsungPayMetadata, ApplePayMetadata, PaypalSdkMetadata, + PazeMetadata, Connector, } @@ -248,15 +253,15 @@ pub enum SessionSurchargeDetails { impl SessionSurchargeDetails { pub fn fetch_surcharge_details( &self, - payment_method: &enums::PaymentMethod, - payment_method_type: &enums::PaymentMethodType, + payment_method: enums::PaymentMethod, + payment_method_type: enums::PaymentMethodType, card_network: Option<&enums::CardNetwork>, ) -> Option { match self { Self::Calculated(surcharge_metadata) => surcharge_metadata .get_surcharge_details(payments_types::SurchargeKey::PaymentMethodData( - *payment_method, - *payment_method_type, + payment_method, + payment_method_type, card_network.cloned(), )) .cloned(), @@ -273,12 +278,12 @@ pub enum ConnectorChoice { impl ConnectorData { pub fn get_connector_by_name( - connectors: &Connectors, + _connectors: &Connectors, name: &str, connector_type: GetToken, connector_id: Option, ) -> CustomResult { - let connector = Self::convert_connector(connectors, name)?; + let connector = Self::convert_connector(name)?; let connector_name = api_enums::Connector::from_str(name) .change_context(errors::ConnectorError::InvalidConnectorName) .change_context(errors::ApiErrorResponse::InternalServerError) @@ -293,12 +298,12 @@ impl ConnectorData { #[cfg(feature = "payouts")] pub fn get_payout_connector_by_name( - connectors: &Connectors, + _connectors: &Connectors, name: &str, connector_type: GetToken, connector_id: Option, ) -> CustomResult { - let connector = Self::convert_connector(connectors, name)?; + let connector = Self::convert_connector(name)?; let payout_connector_name = api_enums::PayoutConnectors::from_str(name) .change_context(errors::ConnectorError::InvalidConnectorName) .change_context(errors::ApiErrorResponse::InternalServerError) @@ -313,7 +318,6 @@ impl ConnectorData { } pub fn convert_connector( - _connectors: &Connectors, connector_name: &str, ) -> CustomResult { match enums::Connector::from_str(connector_name) { @@ -328,6 +332,9 @@ impl ConnectorData { enums::Connector::Airwallex => { Ok(ConnectorEnum::Old(Box::new(&connector::Airwallex))) } + // enums::Connector::Amazonpay => { + // Ok(ConnectorEnum::Old(Box::new(connector::Amazonpay))) + // } enums::Connector::Authorizedotnet => { Ok(ConnectorEnum::Old(Box::new(&connector::Authorizedotnet))) } @@ -363,8 +370,11 @@ impl ConnectorData { enums::Connector::Cryptopay => { Ok(ConnectorEnum::Old(Box::new(connector::Cryptopay::new()))) } + enums::Connector::CtpMastercard => { + Ok(ConnectorEnum::Old(Box::new(&connector::CtpMastercard))) + } enums::Connector::Cybersource => { - Ok(ConnectorEnum::Old(Box::new(&connector::Cybersource))) + Ok(ConnectorEnum::Old(Box::new(connector::Cybersource::new()))) } enums::Connector::Datatrans => { Ok(ConnectorEnum::Old(Box::new(connector::Datatrans::new()))) @@ -372,6 +382,9 @@ impl ConnectorData { enums::Connector::Deutschebank => { Ok(ConnectorEnum::Old(Box::new(connector::Deutschebank::new()))) } + enums::Connector::Digitalvirgo => { + Ok(ConnectorEnum::Old(Box::new(connector::Digitalvirgo::new()))) + } enums::Connector::Dlocal => Ok(ConnectorEnum::Old(Box::new(&connector::Dlocal))), #[cfg(feature = "dummy_connector")] enums::Connector::DummyConnector1 => Ok(ConnectorEnum::Old(Box::new( @@ -404,6 +417,9 @@ impl ConnectorData { enums::Connector::Ebanx => { Ok(ConnectorEnum::Old(Box::new(connector::Ebanx::new()))) } + enums::Connector::Elavon => { + Ok(ConnectorEnum::Old(Box::new(connector::Elavon::new()))) + } enums::Connector::Fiserv => Ok(ConnectorEnum::Old(Box::new(&connector::Fiserv))), enums::Connector::Fiservemea => { Ok(ConnectorEnum::Old(Box::new(connector::Fiservemea::new()))) @@ -425,12 +441,26 @@ impl ConnectorData { enums::Connector::Iatapay => { Ok(ConnectorEnum::Old(Box::new(connector::Iatapay::new()))) } + // enums::Connector::Inespay => { + // Ok(ConnectorEnum::Old(Box::new(connector::Inespay::new()))) + // } enums::Connector::Itaubank => { Ok(ConnectorEnum::Old(Box::new(connector::Itaubank::new()))) } - enums::Connector::Klarna => Ok(ConnectorEnum::Old(Box::new(&connector::Klarna))), - enums::Connector::Mollie => Ok(ConnectorEnum::Old(Box::new(&connector::Mollie))), + enums::Connector::Jpmorgan => { + Ok(ConnectorEnum::Old(Box::new(connector::Jpmorgan::new()))) + } + enums::Connector::Klarna => { + Ok(ConnectorEnum::Old(Box::new(connector::Klarna::new()))) + } + enums::Connector::Mollie => { + Ok(ConnectorEnum::Old(Box::new(connector::Mollie::new()))) + } + enums::Connector::Nexixpay => { + Ok(ConnectorEnum::Old(Box::new(connector::Nexixpay::new()))) + } enums::Connector::Nmi => Ok(ConnectorEnum::Old(Box::new(connector::Nmi::new()))), + // enums::Connector::Nomupay => Ok(ConnectorEnum::Old(Box::new(connector::Nomupay))), enums::Connector::Noon => Ok(ConnectorEnum::Old(Box::new(connector::Noon::new()))), enums::Connector::Novalnet => { Ok(ConnectorEnum::Old(Box::new(connector::Novalnet::new()))) @@ -449,7 +479,7 @@ impl ConnectorData { enums::Connector::Payone => { Ok(ConnectorEnum::Old(Box::new(connector::Payone::new()))) } - enums::Connector::Payu => Ok(ConnectorEnum::Old(Box::new(&connector::Payu))), + enums::Connector::Payu => Ok(ConnectorEnum::Old(Box::new(connector::Payu::new()))), enums::Connector::Placetopay => { Ok(ConnectorEnum::Old(Box::new(connector::Placetopay::new()))) } @@ -462,20 +492,28 @@ impl ConnectorData { enums::Connector::Razorpay => { Ok(ConnectorEnum::Old(Box::new(connector::Razorpay::new()))) } - enums::Connector::Rapyd => Ok(ConnectorEnum::Old(Box::new(&connector::Rapyd))), - enums::Connector::Shift4 => Ok(ConnectorEnum::Old(Box::new(&connector::Shift4))), + enums::Connector::Rapyd => { + Ok(ConnectorEnum::Old(Box::new(connector::Rapyd::new()))) + } + // enums::Connector::Redsys => Ok(ConnectorEnum::Old(Box::new(connector::Redsys))), + enums::Connector::Shift4 => { + Ok(ConnectorEnum::Old(Box::new(connector::Shift4::new()))) + } enums::Connector::Square => Ok(ConnectorEnum::Old(Box::new(&connector::Square))), enums::Connector::Stax => Ok(ConnectorEnum::Old(Box::new(&connector::Stax))), enums::Connector::Stripe => { Ok(ConnectorEnum::Old(Box::new(connector::Stripe::new()))) } - enums::Connector::Wise => Ok(ConnectorEnum::Old(Box::new(&connector::Wise))), + enums::Connector::Wise => Ok(ConnectorEnum::Old(Box::new(connector::Wise::new()))), enums::Connector::Worldline => { Ok(ConnectorEnum::Old(Box::new(&connector::Worldline))) } enums::Connector::Worldpay => { - Ok(ConnectorEnum::Old(Box::new(&connector::Worldpay))) + Ok(ConnectorEnum::Old(Box::new(connector::Worldpay::new()))) } + // enums::Connector::Xendit => { + // Ok(ConnectorEnum::Old(Box::new(connector::Xendit::new()))) + // } enums::Connector::Mifinity => { Ok(ConnectorEnum::Old(Box::new(connector::Mifinity::new()))) } @@ -498,11 +536,13 @@ impl ConnectorData { enums::Connector::Trustpay => { Ok(ConnectorEnum::Old(Box::new(connector::Trustpay::new()))) } - enums::Connector::Tsys => Ok(ConnectorEnum::Old(Box::new(&connector::Tsys))), - + enums::Connector::Tsys => Ok(ConnectorEnum::Old(Box::new(connector::Tsys::new()))), + // enums::Connector::UnifiedAuthenticationService => Ok(ConnectorEnum::Old(Box::new( + // connector::UnifiedAuthenticationService, + // ))), enums::Connector::Volt => Ok(ConnectorEnum::Old(Box::new(connector::Volt::new()))), enums::Connector::Wellsfargo => { - Ok(ConnectorEnum::Old(Box::new(&connector::Wellsfargo))) + Ok(ConnectorEnum::Old(Box::new(connector::Wellsfargo::new()))) } // enums::Connector::Wellsfargopayout => { @@ -547,25 +587,6 @@ pub trait FraudCheck {} #[cfg(not(feature = "frm"))] pub trait FraudCheckV2 {} -#[cfg(feature = "payouts")] -pub trait Payouts: - ConnectorCommon - + PayoutCancel - + PayoutCreate - + PayoutEligibility - + PayoutFulfill - + PayoutQuote - + PayoutRecipient - + PayoutRecipientAccount - + PayoutSync -{ -} -#[cfg(not(feature = "payouts"))] -pub trait Payouts {} - -#[cfg(not(feature = "payouts"))] -pub trait PayoutsV2 {} - #[cfg(test)] mod test { #![allow(clippy::unwrap_used)] diff --git a/crates/router/src/types/api/admin.rs b/crates/router/src/types/api/admin.rs index 8d52e5dc11ab..ff72af484016 100644 --- a/crates/router/src/types/api/admin.rs +++ b/crates/router/src/types/api/admin.rs @@ -4,12 +4,12 @@ use std::collections::HashMap; pub use api_models::admin; pub use api_models::{ admin::{ - MerchantAccountCreate, MerchantAccountDeleteResponse, MerchantAccountResponse, - MerchantAccountUpdate, MerchantConnectorCreate, MerchantConnectorDeleteResponse, - MerchantConnectorDetails, MerchantConnectorDetailsWrap, MerchantConnectorId, - MerchantConnectorResponse, MerchantDetails, MerchantId, PaymentMethodsEnabled, - ProfileCreate, ProfileResponse, ProfileUpdate, ToggleAllKVRequest, ToggleAllKVResponse, - ToggleKVRequest, ToggleKVResponse, WebhookDetails, + MaskedHeaders, MerchantAccountCreate, MerchantAccountDeleteResponse, + MerchantAccountResponse, MerchantAccountUpdate, MerchantConnectorCreate, + MerchantConnectorDeleteResponse, MerchantConnectorDetails, MerchantConnectorDetailsWrap, + MerchantConnectorId, MerchantConnectorResponse, MerchantDetails, MerchantId, + PaymentMethodsEnabled, ProfileCreate, ProfileResponse, ProfileUpdate, ToggleAllKVRequest, + ToggleAllKVResponse, ToggleKVRequest, ToggleKVResponse, WebhookDetails, }, organization::{ OrganizationCreateRequest, OrganizationId, OrganizationResponse, OrganizationUpdateRequest, @@ -34,6 +34,10 @@ use crate::{ impl ForeignFrom for OrganizationResponse { fn foreign_from(org: diesel_models::organization::Organization) -> Self { Self { + #[cfg(feature = "v2")] + id: org.get_organization_id(), + + #[cfg(feature = "v1")] organization_id: org.get_organization_id(), organization_name: org.get_organization_name(), organization_details: org.organization_details, @@ -127,6 +131,8 @@ impl ForeignTryFrom for ProfileResponse { ) }) .transpose()?; + let masked_outgoing_webhook_custom_http_headers = + outgoing_webhook_custom_http_headers.map(MaskedHeaders::from_headers); Ok(Self { merchant_id: item.merchant_id, @@ -164,12 +170,14 @@ impl ForeignTryFrom for ProfileResponse { always_collect_shipping_details_from_wallet_connector: item .always_collect_shipping_details_from_wallet_connector, is_connector_agnostic_mit_enabled: item.is_connector_agnostic_mit_enabled, - outgoing_webhook_custom_http_headers, + outgoing_webhook_custom_http_headers: masked_outgoing_webhook_custom_http_headers, tax_connector_id: item.tax_connector_id, is_tax_connector_enabled: item.is_tax_connector_enabled, is_network_tokenization_enabled: item.is_network_tokenization_enabled, is_auto_retries_enabled: item.is_auto_retries_enabled, max_auto_retries_enabled: item.max_auto_retries_enabled, + is_click_to_pay_enabled: item.is_click_to_pay_enabled, + authentication_product_ids: item.authentication_product_ids, }) } } @@ -198,6 +206,8 @@ impl ForeignTryFrom for ProfileResponse { .map(admin::OrderFulfillmentTime::try_new) .transpose() .change_context(errors::ParsingError::IntegerOverflow)?; + let masked_outgoing_webhook_custom_http_headers = + outgoing_webhook_custom_http_headers.map(MaskedHeaders::from_headers); Ok(Self { merchant_id: item.merchant_id, @@ -230,12 +240,15 @@ impl ForeignTryFrom for ProfileResponse { always_collect_billing_details_from_wallet_connector: item .always_collect_billing_details_from_wallet_connector, is_connector_agnostic_mit_enabled: item.is_connector_agnostic_mit_enabled, - outgoing_webhook_custom_http_headers, + outgoing_webhook_custom_http_headers: masked_outgoing_webhook_custom_http_headers, order_fulfillment_time, order_fulfillment_time_origin: item.order_fulfillment_time_origin, + should_collect_cvv_during_payment: item.should_collect_cvv_during_payment, tax_connector_id: item.tax_connector_id, is_tax_connector_enabled: item.is_tax_connector_enabled, is_network_tokenization_enabled: item.is_network_tokenization_enabled, + is_click_to_pay_enabled: item.is_click_to_pay_enabled, + authentication_product_ids: item.authentication_product_ids, }) } } @@ -265,10 +278,15 @@ pub async fn create_profile_from_merchant_account( .unwrap_or(common_utils::crypto::generate_cryptographically_secure_random_string(64)); let payment_link_config = request.payment_link_config.map(ForeignInto::foreign_into); + let key_manager_state = state.into(); let outgoing_webhook_custom_http_headers = request .outgoing_webhook_custom_http_headers .async_map(|headers| { - core::payment_methods::cards::create_encrypted_data(state, key_store, headers) + core::payment_methods::cards::create_encrypted_data( + &key_manager_state, + key_store, + headers, + ) }) .await .transpose() @@ -287,6 +305,13 @@ pub async fn create_profile_from_merchant_account( }) .transpose()?; + let authentication_product_ids = request + .authentication_product_ids + .map(serde_json::to_value) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("failed to parse product authentication id's to value")?; + Ok(domain::Profile::from(domain::ProfileSetter { profile_id, merchant_id, @@ -357,5 +382,7 @@ pub async fn create_profile_from_merchant_account( is_network_tokenization_enabled: request.is_network_tokenization_enabled, is_auto_retries_enabled: request.is_auto_retries_enabled.unwrap_or_default(), max_auto_retries_enabled: request.max_auto_retries_enabled.map(i16::from), + is_click_to_pay_enabled: request.is_click_to_pay_enabled, + authentication_product_ids, })) } diff --git a/crates/router/src/types/api/authentication.rs b/crates/router/src/types/api/authentication.rs index 7d35943dd793..e82ee116709c 100644 --- a/crates/router/src/types/api/authentication.rs +++ b/crates/router/src/types/api/authentication.rs @@ -150,6 +150,12 @@ impl AuthenticationConnectorData { enums::AuthenticationConnectors::Gpayments => { Ok(ConnectorEnum::Old(Box::new(connector::Gpayments::new()))) } + enums::AuthenticationConnectors::CtpMastercard => { + Ok(ConnectorEnum::Old(Box::new(&connector::CtpMastercard))) + } + enums::AuthenticationConnectors::UnifiedAuthenticationService => Ok( + ConnectorEnum::Old(Box::new(connector::UnifiedAuthenticationService::new())), + ), } } } diff --git a/crates/router/src/types/api/customers.rs b/crates/router/src/types/api/customers.rs index 9cfec6f7b5bb..5b37fb2add6e 100644 --- a/crates/router/src/types/api/customers.rs +++ b/crates/router/src/types/api/customers.rs @@ -1,9 +1,7 @@ use api_models::customers; -#[cfg(all(feature = "v2", feature = "customer_v2"))] -pub use api_models::customers::GlobalId; pub use api_models::customers::{ - CustomerDeleteResponse, CustomerId, CustomerListRequest, CustomerRequest, - CustomerUpdateRequest, UpdateCustomerId, + CustomerDeleteResponse, CustomerListRequest, CustomerRequest, CustomerUpdateRequest, + CustomerUpdateRequestInternal, }; #[cfg(all(feature = "v2", feature = "customer_v2"))] use hyperswitch_domain_models::customer; @@ -50,6 +48,7 @@ impl ForeignFrom<(domain::Customer, Option)> for Custo impl ForeignFrom for CustomerResponse { fn foreign_from(cust: domain::Customer) -> Self { customers::CustomerResponse { + id: cust.id, merchant_reference_id: cust.merchant_reference_id, name: cust.name, email: cust.email, @@ -61,7 +60,6 @@ impl ForeignFrom for CustomerResponse { default_billing_address: None, default_shipping_address: None, default_payment_method_id: cust.default_payment_method_id, - id: cust.id, } .into() } diff --git a/crates/router/src/types/api/fraud_check.rs b/crates/router/src/types/api/fraud_check.rs index 2d1a42092f47..213aef9cf039 100644 --- a/crates/router/src/types/api/fraud_check.rs +++ b/crates/router/src/types/api/fraud_check.rs @@ -51,7 +51,7 @@ impl FraudCheckConnectorData { Ok(ConnectorEnum::Old(Box::new(&connector::Signifyd))) } enums::FrmConnectors::Riskified => { - Ok(ConnectorEnum::Old(Box::new(&connector::Riskified))) + Ok(ConnectorEnum::Old(Box::new(connector::Riskified::new()))) } } } diff --git a/crates/router/src/types/api/mandates.rs b/crates/router/src/types/api/mandates.rs index b3bcfa2a8872..949eac397450 100644 --- a/crates/router/src/types/api/mandates.rs +++ b/crates/router/src/types/api/mandates.rs @@ -56,7 +56,7 @@ impl MandateResponseExt for MandateResponse { .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; let pm = payment_method - .payment_method + .get_payment_method_type() .get_required_value("payment_method") .change_context(errors::ApiErrorResponse::PaymentMethodNotFound) .attach_printable("payment_method not found")?; @@ -91,7 +91,7 @@ impl MandateResponseExt for MandateResponse { None }; let payment_method_type = payment_method - .payment_method_type + .get_payment_method_subtype() .map(|pmt| pmt.to_string()); Ok(Self { mandate_id: mandate.mandate_id, diff --git a/crates/router/src/types/api/payment_methods.rs b/crates/router/src/types/api/payment_methods.rs index d66bf079d42a..25227ae5383a 100644 --- a/crates/router/src/types/api/payment_methods.rs +++ b/crates/router/src/types/api/payment_methods.rs @@ -6,10 +6,11 @@ pub use api_models::payment_methods::{ PaymentMethodCollectLinkRenderRequest, PaymentMethodCollectLinkRequest, PaymentMethodCreate, PaymentMethodCreateData, PaymentMethodDeleteResponse, PaymentMethodId, PaymentMethodIntentConfirm, PaymentMethodIntentConfirmInternal, PaymentMethodIntentCreate, - PaymentMethodList, PaymentMethodListData, PaymentMethodListRequest, PaymentMethodListResponse, - PaymentMethodMigrate, PaymentMethodResponse, PaymentMethodResponseData, PaymentMethodUpdate, - PaymentMethodUpdateData, PaymentMethodsData, TokenizePayloadEncrypted, TokenizePayloadRequest, - TokenizedCardValue1, TokenizedCardValue2, TokenizedWalletValue1, TokenizedWalletValue2, + PaymentMethodListData, PaymentMethodListRequest, PaymentMethodListResponse, + PaymentMethodMigrate, PaymentMethodMigrateResponse, PaymentMethodResponse, + PaymentMethodResponseData, PaymentMethodUpdate, PaymentMethodUpdateData, PaymentMethodsData, + TokenizePayloadEncrypted, TokenizePayloadRequest, TokenizedCardValue1, TokenizedCardValue2, + TokenizedWalletValue1, TokenizedWalletValue2, }; #[cfg(all( any(feature = "v2", feature = "v1"), @@ -20,11 +21,11 @@ pub use api_models::payment_methods::{ CustomerPaymentMethodsListResponse, DefaultPaymentMethod, DeleteTokenizeByTokenRequest, GetTokenizePayloadRequest, GetTokenizePayloadResponse, ListCountriesCurrenciesRequest, PaymentMethodCollectLinkRenderRequest, PaymentMethodCollectLinkRequest, PaymentMethodCreate, - PaymentMethodCreateData, PaymentMethodDeleteResponse, PaymentMethodId, PaymentMethodList, + PaymentMethodCreateData, PaymentMethodDeleteResponse, PaymentMethodId, PaymentMethodListRequest, PaymentMethodListResponse, PaymentMethodMigrate, - PaymentMethodResponse, PaymentMethodUpdate, PaymentMethodsData, TokenizePayloadEncrypted, - TokenizePayloadRequest, TokenizedCardValue1, TokenizedCardValue2, TokenizedWalletValue1, - TokenizedWalletValue2, + PaymentMethodMigrateResponse, PaymentMethodResponse, PaymentMethodUpdate, PaymentMethodsData, + TokenizePayloadEncrypted, TokenizePayloadRequest, TokenizedCardValue1, TokenizedCardValue2, + TokenizedWalletValue1, TokenizedWalletValue2, }; use error_stack::report; @@ -65,8 +66,8 @@ impl PaymentMethodCreateExt for PaymentMethodCreate { fn validate(&self) -> RouterResult<()> { utils::when( !validate_payment_method_type_against_payment_method( - self.payment_method, self.payment_method_type, + self.payment_method_subtype, ), || { Err(report!(errors::ApiErrorResponse::InvalidRequestData { @@ -78,7 +79,7 @@ impl PaymentMethodCreateExt for PaymentMethodCreate { utils::when( !Self::validate_payment_method_data_against_payment_method( - self.payment_method, + self.payment_method_type, self.payment_method_data.clone(), ), || { @@ -97,8 +98,8 @@ impl PaymentMethodCreateExt for PaymentMethodIntentConfirm { fn validate(&self) -> RouterResult<()> { utils::when( !validate_payment_method_type_against_payment_method( - self.payment_method, self.payment_method_type, + self.payment_method_subtype, ), || { Err(report!(errors::ApiErrorResponse::InvalidRequestData { @@ -110,7 +111,7 @@ impl PaymentMethodCreateExt for PaymentMethodIntentConfirm { utils::when( !Self::validate_payment_method_data_against_payment_method( - self.payment_method, + self.payment_method_type, self.payment_method_data.clone(), ), || { diff --git a/crates/router/src/types/api/payments.rs b/crates/router/src/types/api/payments.rs index 575725d48741..6ec0fe701e9c 100644 --- a/crates/router/src/types/api/payments.rs +++ b/crates/router/src/types/api/payments.rs @@ -1,51 +1,71 @@ +#[cfg(feature = "v1")] +pub use api_models::payments::PaymentsRequest; +#[cfg(feature = "v2")] pub use api_models::payments::{ - AcceptanceType, Address, AddressDetails, Amount, AuthenticationForStartResponse, Card, - CryptoData, CustomerAcceptance, CustomerDetailsResponse, HeaderPayload, MandateAmountData, - MandateData, MandateTransactionType, MandateType, MandateValidationFields, NextActionType, - OnlineMandate, OpenBankingSessionToken, PayLaterData, PaymentIdType, PaymentListConstraints, - PaymentListFilterConstraints, PaymentListFilters, PaymentListFiltersV2, PaymentListResponse, - PaymentListResponseV2, PaymentMethodData, PaymentMethodDataRequest, PaymentMethodDataResponse, - PaymentOp, PaymentRetrieveBody, PaymentRetrieveBodyWithCredentials, PaymentsAggregateResponse, - PaymentsApproveRequest, PaymentsCancelRequest, PaymentsCaptureRequest, - PaymentsCompleteAuthorizeRequest, PaymentsDynamicTaxCalculationRequest, - PaymentsDynamicTaxCalculationResponse, PaymentsExternalAuthenticationRequest, - PaymentsIncrementalAuthorizationRequest, PaymentsManualUpdateRequest, PaymentsRedirectRequest, - PaymentsRedirectionResponse, PaymentsRejectRequest, PaymentsRequest, PaymentsResponse, - PaymentsResponseForm, PaymentsRetrieveRequest, PaymentsSessionRequest, PaymentsSessionResponse, - PaymentsStartRequest, PgRedirectResponse, PhoneDetails, RedirectionResponse, SessionToken, - UrlDetails, VerifyRequest, VerifyResponse, WalletData, + PaymentsCreateIntentRequest, PaymentsIntentResponse, PaymentsUpdateIntentRequest, +}; +pub use api_models::{ + feature_matrix::{ + ConnectorFeatureMatrixResponse, FeatureMatrixListResponse, FeatureMatrixRequest, + }, + payments::{ + AcceptanceType, Address, AddressDetails, Amount, AuthenticationForStartResponse, Card, + CryptoData, CustomerAcceptance, CustomerDetailsResponse, MandateAmountData, MandateData, + MandateTransactionType, MandateType, MandateValidationFields, NextActionType, + OnlineMandate, OpenBankingSessionToken, PayLaterData, PaymentIdType, + PaymentListConstraints, PaymentListFilterConstraints, PaymentListFilters, + PaymentListFiltersV2, PaymentListResponse, PaymentListResponseV2, PaymentMethodData, + PaymentMethodDataRequest, PaymentMethodDataResponse, PaymentOp, PaymentRetrieveBody, + PaymentRetrieveBodyWithCredentials, PaymentsAggregateResponse, PaymentsApproveRequest, + PaymentsCancelRequest, PaymentsCaptureRequest, PaymentsCompleteAuthorizeRequest, + PaymentsDynamicTaxCalculationRequest, PaymentsDynamicTaxCalculationResponse, + PaymentsExternalAuthenticationRequest, PaymentsIncrementalAuthorizationRequest, + PaymentsManualUpdateRequest, PaymentsPostSessionTokensRequest, + PaymentsPostSessionTokensResponse, PaymentsRedirectRequest, PaymentsRedirectionResponse, + PaymentsRejectRequest, PaymentsResponse, PaymentsResponseForm, PaymentsRetrieveRequest, + PaymentsSessionRequest, PaymentsSessionResponse, PaymentsStartRequest, PgRedirectResponse, + PhoneDetails, RedirectionResponse, SessionToken, UrlDetails, VerifyRequest, VerifyResponse, + WalletData, + }, }; use error_stack::ResultExt; pub use hyperswitch_domain_models::router_flow_types::payments::{ Approve, Authorize, AuthorizeSessionToken, Balance, CalculateTax, Capture, CompleteAuthorize, - CreateConnectorCustomer, IncrementalAuthorization, InitPayment, PSync, PaymentMethodToken, - PostProcessing, PreProcessing, Reject, SdkSessionUpdate, Session, SetupMandate, Void, + CreateConnectorCustomer, IncrementalAuthorization, InitPayment, PSync, PaymentCreateIntent, + PaymentGetIntent, PaymentMethodToken, PaymentUpdateIntent, PostProcessing, PostSessionTokens, + PreProcessing, Reject, SdkSessionUpdate, Session, SetupMandate, Void, }; pub use hyperswitch_interfaces::api::payments::{ ConnectorCustomer, MandateSetup, Payment, PaymentApprove, PaymentAuthorize, - PaymentAuthorizeSessionToken, PaymentCapture, PaymentIncrementalAuthorization, PaymentReject, - PaymentSession, PaymentSessionUpdate, PaymentSync, PaymentToken, PaymentVoid, - PaymentsCompleteAuthorize, PaymentsPostProcessing, PaymentsPreProcessing, TaxCalculation, + PaymentAuthorizeSessionToken, PaymentCapture, PaymentIncrementalAuthorization, + PaymentPostSessionTokens, PaymentReject, PaymentSession, PaymentSessionUpdate, PaymentSync, + PaymentToken, PaymentVoid, PaymentsCompleteAuthorize, PaymentsPostProcessing, + PaymentsPreProcessing, TaxCalculation, }; pub use super::payments_v2::{ ConnectorCustomerV2, MandateSetupV2, PaymentApproveV2, PaymentAuthorizeSessionTokenV2, - PaymentAuthorizeV2, PaymentCaptureV2, PaymentIncrementalAuthorizationV2, PaymentRejectV2, - PaymentSessionUpdateV2, PaymentSessionV2, PaymentSyncV2, PaymentTokenV2, PaymentV2, - PaymentVoidV2, PaymentsCompleteAuthorizeV2, PaymentsPostProcessingV2, PaymentsPreProcessingV2, - TaxCalculationV2, + PaymentAuthorizeV2, PaymentCaptureV2, PaymentIncrementalAuthorizationV2, + PaymentPostSessionTokensV2, PaymentRejectV2, PaymentSessionUpdateV2, PaymentSessionV2, + PaymentSyncV2, PaymentTokenV2, PaymentV2, PaymentVoidV2, PaymentsCompleteAuthorizeV2, + PaymentsPostProcessingV2, PaymentsPreProcessingV2, TaxCalculationV2, }; use crate::core::errors; -impl super::Router for PaymentsRequest {} - pub trait PaymentIdTypeExt { + #[cfg(feature = "v1")] fn get_payment_intent_id( &self, ) -> errors::CustomResult; + + #[cfg(feature = "v2")] + fn get_payment_intent_id( + &self, + ) -> errors::CustomResult; } impl PaymentIdTypeExt for PaymentIdType { + #[cfg(feature = "v1")] fn get_payment_intent_id( &self, ) -> errors::CustomResult { @@ -59,6 +79,21 @@ impl PaymentIdTypeExt for PaymentIdType { .attach_printable("Expected payment intent ID but got connector transaction ID"), } } + + #[cfg(feature = "v2")] + fn get_payment_intent_id( + &self, + ) -> errors::CustomResult { + match self { + Self::PaymentIntentId(id) => Ok(id.clone()), + Self::ConnectorTransactionId(_) + | Self::PaymentAttemptId(_) + | Self::PreprocessingId(_) => Err(errors::ValidationError::IncorrectValueProvided { + field_name: "payment_id", + }) + .attach_printable("Expected payment intent ID but got connector transaction ID"), + } + } } pub(crate) trait MandateValidationFieldsExt { @@ -84,6 +119,7 @@ impl MandateValidationFieldsExt for MandateValidationFields { } } +#[cfg(feature = "v1")] #[cfg(test)] mod payments_test { #![allow(clippy::expect_used, clippy::unwrap_used)] diff --git a/crates/router/src/types/api/payments_v2.rs b/crates/router/src/types/api/payments_v2.rs index 59f132c0154f..b28cfcf28123 100644 --- a/crates/router/src/types/api/payments_v2.rs +++ b/crates/router/src/types/api/payments_v2.rs @@ -1,7 +1,7 @@ pub use hyperswitch_interfaces::api::payments_v2::{ ConnectorCustomerV2, MandateSetupV2, PaymentApproveV2, PaymentAuthorizeSessionTokenV2, - PaymentAuthorizeV2, PaymentCaptureV2, PaymentIncrementalAuthorizationV2, PaymentRejectV2, - PaymentSessionUpdateV2, PaymentSessionV2, PaymentSyncV2, PaymentTokenV2, PaymentV2, - PaymentVoidV2, PaymentsCompleteAuthorizeV2, PaymentsPostProcessingV2, PaymentsPreProcessingV2, - TaxCalculationV2, + PaymentAuthorizeV2, PaymentCaptureV2, PaymentIncrementalAuthorizationV2, + PaymentPostSessionTokensV2, PaymentRejectV2, PaymentSessionUpdateV2, PaymentSessionV2, + PaymentSyncV2, PaymentTokenV2, PaymentV2, PaymentVoidV2, PaymentsCompleteAuthorizeV2, + PaymentsPostProcessingV2, PaymentsPreProcessingV2, TaxCalculationV2, }; diff --git a/crates/router/src/types/api/payouts.rs b/crates/router/src/types/api/payouts.rs index a9630beb25a4..b5a6ac24ca9e 100644 --- a/crates/router/src/types/api/payouts.rs +++ b/crates/router/src/types/api/payouts.rs @@ -11,7 +11,7 @@ pub use hyperswitch_domain_models::router_flow_types::payouts::{ }; pub use hyperswitch_interfaces::api::payouts::{ PayoutCancel, PayoutCreate, PayoutEligibility, PayoutFulfill, PayoutQuote, PayoutRecipient, - PayoutRecipientAccount, PayoutSync, + PayoutRecipientAccount, PayoutSync, Payouts, }; pub use super::payouts_v2::{ diff --git a/crates/router/src/types/api/verify_connector.rs b/crates/router/src/types/api/verify_connector.rs index 6c92b1b801a9..47d8add58c3b 100644 --- a/crates/router/src/types/api/verify_connector.rs +++ b/crates/router/src/types/api/verify_connector.rs @@ -29,6 +29,7 @@ impl VerifyConnectorData { amount: 1000, minor_amount: common_utils::types::MinorUnit::new(1000), confirm: true, + order_tax_amount: None, currency: storage_enums::Currency::USD, metadata: None, mandate_id: None, @@ -54,9 +55,11 @@ impl VerifyConnectorData { request_incremental_authorization: false, authentication_data: None, customer_acceptance: None, - charges: None, + split_payments: None, merchant_order_reference_id: None, integrity_object: None, + additional_payment_method_data: None, + shipping_cost: None, } } @@ -75,7 +78,6 @@ impl VerifyConnectorData { connector: self.connector.id().to_string(), auth_type: storage_enums::AuthenticationType::NoThreeDs, test_mode: None, - return_url: None, attempt_id: attempt_id.clone(), description: None, customer_id: None, @@ -115,6 +117,9 @@ impl VerifyConnectorData { integrity_check: Ok(()), additional_merchant_data: None, header_payload: None, + connector_mandate_request_reference_id: None, + authentication_id: None, + psd2_sca_exemption_type: None, } } } diff --git a/crates/router/src/types/domain/address.rs b/crates/router/src/types/domain/address.rs index 5d9af527cd4b..2d1e98ec1b8c 100644 --- a/crates/router/src/types/domain/address.rs +++ b/crates/router/src/types/domain/address.rs @@ -4,30 +4,38 @@ use common_utils::{ date_time, encryption::Encryption, errors::{CustomResult, ValidationError}, - id_type, type_name, + id_type, pii, type_name, types::keymanager::{Identifier, KeyManagerState, ToEncryptable}, }; use diesel_models::{address::AddressUpdateInternal, enums}; use error_stack::ResultExt; -use masking::{PeekInterface, Secret}; +use masking::{PeekInterface, Secret, SwitchStrategy}; use rustc_hash::FxHashMap; use time::{OffsetDateTime, PrimitiveDateTime}; use super::{behaviour, types}; -#[derive(Clone, Debug, serde::Serialize)] +#[derive(Clone, Debug, serde::Serialize, router_derive::ToEncryption)] pub struct Address { pub address_id: String, pub city: Option, pub country: Option, - pub line1: crypto::OptionalEncryptableSecretString, - pub line2: crypto::OptionalEncryptableSecretString, - pub line3: crypto::OptionalEncryptableSecretString, - pub state: crypto::OptionalEncryptableSecretString, - pub zip: crypto::OptionalEncryptableSecretString, - pub first_name: crypto::OptionalEncryptableSecretString, - pub last_name: crypto::OptionalEncryptableSecretString, - pub phone_number: crypto::OptionalEncryptableSecretString, + #[encrypt] + pub line1: Option>>, + #[encrypt] + pub line2: Option>>, + #[encrypt] + pub line3: Option>>, + #[encrypt] + pub state: Option>>, + #[encrypt] + pub zip: Option>>, + #[encrypt] + pub first_name: Option>>, + #[encrypt] + pub last_name: Option>>, + #[encrypt] + pub phone_number: Option>>, pub country_code: Option, #[serde(skip_serializing)] #[serde(with = "custom_serde::iso8601")] @@ -37,7 +45,8 @@ pub struct Address { pub modified_at: PrimitiveDateTime, pub merchant_id: id_type::MerchantId, pub updated_by: String, - pub email: crypto::OptionalEncryptableEmail, + #[encrypt] + pub email: Option>>, } /// Based on the flow, appropriate address has to be used @@ -191,8 +200,18 @@ impl behaviour::Conversion for Address { let decrypted: FxHashMap>> = types::crypto_operation( state, type_name!(Self::DstType), - types::CryptoOperation::BatchDecrypt(diesel_models::Address::to_encryptable( - other.clone(), + types::CryptoOperation::BatchDecrypt(EncryptedAddress::to_encryptable( + EncryptedAddress { + line1: other.line1, + line2: other.line2, + line3: other.line3, + state: other.state, + zip: other.zip, + first_name: other.first_name, + last_name: other.last_name, + phone_number: other.phone_number, + email: other.email, + }, )), identifier.clone(), key.peek(), @@ -202,10 +221,11 @@ impl behaviour::Conversion for Address { .change_context(ValidationError::InvalidValue { message: "Failed while decrypting".to_string(), })?; - let encryptable_address = diesel_models::Address::from_encryptable(decrypted) - .change_context(ValidationError::InvalidValue { + let encryptable_address = EncryptedAddress::from_encryptable(decrypted).change_context( + ValidationError::InvalidValue { message: "Failed while decrypting".to_string(), - })?; + }, + )?; Ok(Self { address_id: other.address_id, city: other.city, @@ -223,7 +243,13 @@ impl behaviour::Conversion for Address { modified_at: other.modified_at, updated_by: other.updated_by, merchant_id: other.merchant_id, - email: encryptable_address.email, + email: encryptable_address.email.map(|email| { + let encryptable: Encryptable> = Encryptable::new( + email.clone().into_inner().switch_strategy(), + email.into_encrypted(), + ); + encryptable + }), }) } diff --git a/crates/router/src/types/domain/event.rs b/crates/router/src/types/domain/event.rs index badbb9df7b4f..db2d75301643 100644 --- a/crates/router/src/types/domain/event.rs +++ b/crates/router/src/types/domain/event.rs @@ -1,22 +1,23 @@ use common_utils::{ - crypto::OptionalEncryptableSecretString, + crypto::{Encryptable, OptionalEncryptableSecretString}, + encryption::Encryption, type_name, types::keymanager::{KeyManagerState, ToEncryptable}, }; use diesel_models::{ enums::{EventClass, EventObjectType, EventType, WebhookDeliveryAttempt}, events::{EventMetadata, EventUpdateInternal}, - EventWithEncryption, }; use error_stack::ResultExt; use masking::{PeekInterface, Secret}; +use rustc_hash::FxHashMap; use crate::{ errors::{CustomResult, ValidationError}, types::domain::types, }; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, router_derive::ToEncryption)] pub struct Event { pub event_id: String, pub event_type: EventType, @@ -30,8 +31,10 @@ pub struct Event { pub primary_object_created_at: Option, pub idempotent_event_id: Option, pub initial_attempt_id: Option, - pub request: OptionalEncryptableSecretString, - pub response: OptionalEncryptableSecretString, + #[encrypt] + pub request: Option>>, + #[encrypt] + pub response: Option>>, pub delivery_attempt: Option, pub metadata: Option, } @@ -96,12 +99,10 @@ impl super::behaviour::Conversion for Event { let decrypted = types::crypto_operation( state, type_name!(Self::DstType), - types::CryptoOperation::BatchDecrypt(EventWithEncryption::to_encryptable( - EventWithEncryption { - request: item.request.clone(), - response: item.response.clone(), - }, - )), + types::CryptoOperation::BatchDecrypt(EncryptedEvent::to_encryptable(EncryptedEvent { + request: item.request.clone(), + response: item.response.clone(), + })), key_manager_identifier, key.peek(), ) @@ -110,7 +111,7 @@ impl super::behaviour::Conversion for Event { .change_context(ValidationError::InvalidValue { message: "Failed while decrypting event data".to_string(), })?; - let encryptable_event = EventWithEncryption::from_encryptable(decrypted).change_context( + let encryptable_event = EncryptedEvent::from_encryptable(decrypted).change_context( ValidationError::InvalidValue { message: "Failed while decrypting event data".to_string(), }, diff --git a/crates/router/src/types/domain/types.rs b/crates/router/src/types/domain/types.rs index 082e8df4bfc9..d4cd9ef62d7d 100644 --- a/crates/router/src/types/domain/types.rs +++ b/crates/router/src/types/domain/types.rs @@ -1,6 +1,6 @@ use common_utils::types::keymanager::KeyManagerState; pub use hyperswitch_domain_models::type_encryption::{ - crypto_operation, AsyncLift, CryptoOperation, Lift, + crypto_operation, AsyncLift, CryptoOperation, Lift, OptionalEncryptableJsonType, }; impl From<&crate::SessionState> for KeyManagerState { diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs index 19d682aeef1f..6efaac7bfedc 100644 --- a/crates/router/src/types/domain/user.rs +++ b/crates/router/src/types/domain/user.rs @@ -1,4 +1,8 @@ -use std::{collections::HashSet, ops, str::FromStr}; +use std::{ + collections::HashSet, + ops::{Deref, Not}, + str::FromStr, +}; use api_models::{ admin as admin_api, organization as api_org, user as user_api, user_role as user_role_api, @@ -30,9 +34,9 @@ use crate::{ admin, errors::{UserErrors, UserResult}, }, - db::{user_role::InsertUserRolePayload, GlobalStorageInterface}, + db::GlobalStorageInterface, routes::SessionState, - services::{self, authentication::UserFromToken, authorization::info}, + services::{self, authentication::UserFromToken}, types::transformers::ForeignFrom, utils::user::password, }; @@ -99,7 +103,7 @@ impl UserEmail { pub fn new(email: Secret) -> UserResult { use validator::ValidateEmail; - let email_string = email.expose(); + let email_string = email.expose().to_lowercase(); let email = pii::Email::from_str(&email_string).change_context(UserErrors::EmailParsingError)?; @@ -119,27 +123,18 @@ impl UserEmail { } pub fn from_pii_email(email: pii::Email) -> UserResult { - use validator::ValidateEmail; - - let email_string = email.peek(); - if email_string.validate_email() { - let (_username, domain) = match email_string.split_once('@') { - Some((u, d)) => (u, d), - None => return Err(UserErrors::EmailParsingError.into()), - }; - if BLOCKED_EMAIL.contains(domain) { - return Err(UserErrors::InvalidEmailError.into()); - } - Ok(Self(email)) - } else { - Err(UserErrors::EmailParsingError.into()) - } + let email_string = email.expose().map(|inner| inner.to_lowercase()); + Self::new(email_string) } pub fn into_inner(self) -> pii::Email { self.0 } + pub fn get_inner(&self) -> &pii::Email { + &self.0 + } + pub fn get_secret(self) -> Secret { (*self.0).clone() } @@ -153,7 +148,7 @@ impl TryFrom for UserEmail { } } -impl ops::Deref for UserEmail { +impl Deref for UserEmail { type Target = Secret; fn deref(&self) -> &Self::Target { @@ -317,6 +312,40 @@ impl From for NewUserOrganization { } } +impl From<(user_api::CreateTenantUserRequest, MerchantAccountIdentifier)> for NewUserOrganization { + fn from( + (_value, merchant_account_identifier): ( + user_api::CreateTenantUserRequest, + MerchantAccountIdentifier, + ), + ) -> Self { + let new_organization = api_org::OrganizationNew { + org_id: merchant_account_identifier.org_id, + org_name: None, + }; + let db_organization = ForeignFrom::foreign_from(new_organization); + Self(db_organization) + } +} + +impl ForeignFrom + for diesel_models::organization::OrganizationNew +{ + fn foreign_from(item: api_models::user::UserOrgMerchantCreateRequest) -> Self { + let org_id = id_type::OrganizationId::default(); + let api_models::user::UserOrgMerchantCreateRequest { + organization_name, + organization_details, + metadata, + .. + } = item; + let mut org_new_db = Self::new(org_id, Some(organization_name.expose())); + org_new_db.organization_details = organization_details; + org_new_db.metadata = metadata; + org_new_db + } +} + #[derive(Clone)] pub struct MerchantId(String); @@ -540,6 +569,18 @@ impl TryFrom for NewUserMerchant { } } +impl From<(user_api::CreateTenantUserRequest, MerchantAccountIdentifier)> for NewUserMerchant { + fn from(value: (user_api::CreateTenantUserRequest, MerchantAccountIdentifier)) -> Self { + let merchant_id = value.1.merchant_id.clone(); + let new_organization = NewUserOrganization::from(value); + Self { + company_name: None, + merchant_id, + new_organization, + } + } +} + type UserMerchantCreateRequestWithToken = (UserFromStorage, user_api::UserMerchantCreate, UserFromToken); @@ -560,15 +601,35 @@ impl TryFrom for NewUserMerchant { } } +#[derive(Debug, Clone)] +pub struct MerchantAccountIdentifier { + pub merchant_id: id_type::MerchantId, + pub org_id: id_type::OrganizationId, +} + #[derive(Clone)] pub struct NewUser { user_id: String, name: UserName, email: UserEmail, - password: Option, + password: Option, new_merchant: NewUserMerchant, } +#[derive(Clone)] +pub struct NewUserPassword { + password: UserPassword, + is_temporary: bool, +} + +impl Deref for NewUserPassword { + type Target = UserPassword; + + fn deref(&self) -> &Self::Target { + &self.password + } +} + impl NewUser { pub fn get_user_id(&self) -> String { self.user_id.clone() @@ -587,7 +648,9 @@ impl NewUser { } pub fn get_password(&self) -> Option { - self.password.clone() + self.password + .as_ref() + .map(|password| password.deref().clone()) } pub async fn insert_user_in_db( @@ -610,7 +673,7 @@ impl NewUser { pub async fn check_if_already_exists_in_db(&self, state: SessionState) -> UserResult<()> { if state .global_store - .find_user_by_email(&self.get_email().into_inner()) + .find_user_by_email(&self.get_email()) .await .is_ok() { @@ -661,26 +724,20 @@ impl NewUser { state: SessionState, role_id: String, user_status: UserStatus, - version: Option, ) -> UserResult { let org_id = self .get_new_merchant() .get_new_organization() .get_organization_id(); - let merchant_id = self.get_new_merchant().get_merchant_id(); let org_user_role = self .get_no_level_user_role(role_id, user_status) .add_entity(OrganizationLevel { + tenant_id: state.tenant.tenant_id.clone(), org_id, - merchant_id, }); - match version { - Some(UserRoleVersion::V1) => org_user_role.insert_in_v1(&state).await, - Some(UserRoleVersion::V2) => org_user_role.insert_in_v2(&state).await, - None => org_user_role.insert_in_v1_and_v2(&state).await, - } + org_user_role.insert_in_v2(&state).await } } @@ -706,7 +763,9 @@ impl TryFrom for storage_user::UserNew { totp_status: TotpStatus::NotSet, totp_secret: None, totp_recovery_codes: None, - last_password_modified_at: value.password.is_some().then_some(now), + last_password_modified_at: value + .password + .and_then(|password_inner| password_inner.is_temporary.not().then_some(now)), }) } } @@ -717,7 +776,10 @@ impl TryFrom for NewUser { fn try_from(value: user_api::SignUpWithMerchantIdRequest) -> UserResult { let email = value.email.clone().try_into()?; let name = UserName::new(value.name.clone())?; - let password = UserPassword::new(value.password.clone())?; + let password = NewUserPassword { + password: UserPassword::new(value.password.clone())?, + is_temporary: false, + }; let user_id = uuid::Uuid::new_v4().to_string(); let new_merchant = NewUserMerchant::try_from(value)?; @@ -738,7 +800,10 @@ impl TryFrom for NewUser { let user_id = uuid::Uuid::new_v4().to_string(); let email = value.email.clone().try_into()?; let name = UserName::try_from(value.email.clone())?; - let password = UserPassword::new(value.password.clone())?; + let password = NewUserPassword { + password: UserPassword::new(value.password.clone())?, + is_temporary: false, + }; let new_merchant = NewUserMerchant::try_from(value)?; Ok(Self { @@ -779,7 +844,10 @@ impl TryFrom<(user_api::CreateInternalUserRequest, id_type::OrganizationId)> for let user_id = uuid::Uuid::new_v4().to_string(); let email = value.email.clone().try_into()?; let name = UserName::new(value.name.clone())?; - let password = UserPassword::new(value.password.clone())?; + let password = NewUserPassword { + password: UserPassword::new(value.password.clone())?, + is_temporary: false, + }; let new_merchant = NewUserMerchant::try_from((value, org_id))?; Ok(Self { @@ -798,16 +866,21 @@ impl TryFrom for NewUser { fn try_from(value: UserMerchantCreateRequestWithToken) -> Result { let user = value.0.clone(); let new_merchant = NewUserMerchant::try_from(value)?; + let password = user + .0 + .password + .map(UserPassword::new_password_without_validation) + .transpose()? + .map(|password| NewUserPassword { + password, + is_temporary: false, + }); Ok(Self { user_id: user.0.user_id, name: UserName::new(user.0.name)?, email: user.0.email.clone().try_into()?, - password: user - .0 - .password - .map(UserPassword::new_password_without_validation) - .transpose()?, + password, new_merchant, }) } @@ -819,8 +892,10 @@ impl TryFrom for NewUser { let user_id = uuid::Uuid::new_v4().to_string(); let email = value.0.email.clone().try_into()?; let name = UserName::new(value.0.name.clone())?; - let password = cfg!(not(feature = "email")) - .then_some(UserPassword::new(password::get_temp_password())?); + let password = cfg!(not(feature = "email")).then_some(NewUserPassword { + password: UserPassword::new(password::get_temp_password())?, + is_temporary: true, + }); let new_merchant = NewUserMerchant::try_from(value)?; Ok(Self { @@ -833,6 +908,34 @@ impl TryFrom for NewUser { } } +impl TryFrom<(user_api::CreateTenantUserRequest, MerchantAccountIdentifier)> for NewUser { + type Error = error_stack::Report; + + fn try_from( + (value, merchant_account_identifier): ( + user_api::CreateTenantUserRequest, + MerchantAccountIdentifier, + ), + ) -> UserResult { + let user_id = uuid::Uuid::new_v4().to_string(); + let email = value.email.clone().try_into()?; + let name = UserName::new(value.name.clone())?; + let password = NewUserPassword { + password: UserPassword::new(value.password.clone())?, + is_temporary: false, + }; + let new_merchant = NewUserMerchant::from((value, merchant_account_identifier)); + + Ok(Self { + user_id, + name, + email, + password: Some(password), + new_merchant, + }) + } +} + #[derive(Clone)] pub struct UserFromStorage(pub storage_user::User); @@ -1019,37 +1122,6 @@ impl UserFromStorage { } } -impl From for user_role_api::ModuleInfo { - fn from(value: info::ModuleInfo) -> Self { - Self { - module: value.module.into(), - description: value.description, - permissions: value.permissions.into_iter().map(Into::into).collect(), - } - } -} - -impl From for user_role_api::PermissionModule { - fn from(value: info::PermissionModule) -> Self { - match value { - info::PermissionModule::Payments => Self::Payments, - info::PermissionModule::Refunds => Self::Refunds, - info::PermissionModule::MerchantAccount => Self::MerchantAccount, - info::PermissionModule::Connectors => Self::Connectors, - info::PermissionModule::Routing => Self::Routing, - info::PermissionModule::Analytics => Self::Analytics, - info::PermissionModule::Mandates => Self::Mandates, - info::PermissionModule::Customer => Self::Customer, - info::PermissionModule::Disputes => Self::Disputes, - info::PermissionModule::ThreeDsDecisionManager => Self::ThreeDsDecisionManager, - info::PermissionModule::SurchargeDecisionManager => Self::SurchargeDecisionManager, - info::PermissionModule::AccountCreate => Self::AccountCreate, - info::PermissionModule::Payouts => Self::Payouts, - info::PermissionModule::Recon => Self::Recon, - } - } -} - impl ForeignFrom for user_role_api::UserStatus { fn foreign_from(value: UserStatus) -> Self { match value { @@ -1116,21 +1188,27 @@ impl RecoveryCodes { #[derive(Clone)] pub struct NoLevel; +#[derive(Clone)] +pub struct TenantLevel { + pub tenant_id: id_type::TenantId, +} + #[derive(Clone)] pub struct OrganizationLevel { + pub tenant_id: id_type::TenantId, pub org_id: id_type::OrganizationId, - // Keeping this to allow insertion of org_admins in V1 - pub merchant_id: id_type::MerchantId, } #[derive(Clone)] pub struct MerchantLevel { + pub tenant_id: id_type::TenantId, pub org_id: id_type::OrganizationId, pub merchant_id: id_type::MerchantId, } #[derive(Clone)] pub struct ProfileLevel { + pub tenant_id: id_type::TenantId, pub org_id: id_type::OrganizationId, pub merchant_id: id_type::MerchantId, pub profile_id: id_type::ProfileId, @@ -1167,39 +1245,70 @@ impl NewUserRole { } pub struct EntityInfo { - org_id: id_type::OrganizationId, + tenant_id: id_type::TenantId, + org_id: Option, merchant_id: Option, profile_id: Option, entity_id: String, entity_type: EntityType, } -impl NewUserRole -where - E: Clone, -{ - fn convert_to_new_v1_role( - self, - org_id: id_type::OrganizationId, - merchant_id: id_type::MerchantId, - ) -> UserRoleNew { - UserRoleNew { - user_id: self.user_id, - role_id: self.role_id, - status: self.status, - created_by: self.created_by, - last_modified_by: self.last_modified_by, - created_at: self.created_at, - last_modified: self.last_modified, - org_id: Some(org_id), - merchant_id: Some(merchant_id), +impl From for EntityInfo { + fn from(value: TenantLevel) -> Self { + Self { + entity_id: value.tenant_id.get_string_repr().to_owned(), + entity_type: EntityType::Tenant, + tenant_id: value.tenant_id, + org_id: None, + merchant_id: None, + profile_id: None, + } + } +} + +impl From for EntityInfo { + fn from(value: OrganizationLevel) -> Self { + Self { + entity_id: value.org_id.get_string_repr().to_owned(), + entity_type: EntityType::Organization, + tenant_id: value.tenant_id, + org_id: Some(value.org_id), + merchant_id: None, + profile_id: None, + } + } +} + +impl From for EntityInfo { + fn from(value: MerchantLevel) -> Self { + Self { + entity_id: value.merchant_id.get_string_repr().to_owned(), + entity_type: EntityType::Merchant, + tenant_id: value.tenant_id, + org_id: Some(value.org_id), + merchant_id: Some(value.merchant_id), profile_id: None, - entity_id: None, - entity_type: None, - version: UserRoleVersion::V1, } } +} + +impl From for EntityInfo { + fn from(value: ProfileLevel) -> Self { + Self { + entity_id: value.profile_id.get_string_repr().to_owned(), + entity_type: EntityType::Profile, + tenant_id: value.tenant_id, + org_id: Some(value.org_id), + merchant_id: Some(value.merchant_id), + profile_id: Some(value.profile_id), + } + } +} +impl NewUserRole +where + E: Clone + Into, +{ fn convert_to_new_v2_role(self, entity: EntityInfo) -> UserRoleNew { UserRoleNew { user_id: self.user_id, @@ -1209,125 +1318,25 @@ where last_modified_by: self.last_modified_by, created_at: self.created_at, last_modified: self.last_modified, - org_id: Some(entity.org_id), + org_id: entity.org_id, merchant_id: entity.merchant_id, profile_id: entity.profile_id, entity_id: Some(entity.entity_id), entity_type: Some(entity.entity_type), version: UserRoleVersion::V2, + tenant_id: entity.tenant_id, } } - async fn insert_v1_and_v2_in_db_and_get_v2( - state: &SessionState, - v1_role: UserRoleNew, - v2_role: UserRoleNew, - ) -> UserResult { - let inserted_roles = state - .store - .insert_user_role(InsertUserRolePayload::V1AndV2(Box::new([v1_role, v2_role]))) - .await - .change_context(UserErrors::InternalServerError)?; - - inserted_roles - .into_iter() - .find(|role| role.version == UserRoleVersion::V2) - .ok_or(report!(UserErrors::InternalServerError)) - } -} - -impl NewUserRole { - pub async fn insert_in_v1(self, state: &SessionState) -> UserResult { - let entity = self.entity.clone(); - - let new_v1_role = self - .clone() - .convert_to_new_v1_role(entity.org_id.clone(), entity.merchant_id.clone()); - - state - .store - .insert_user_role(InsertUserRolePayload::OnlyV1(new_v1_role)) - .await - .change_context(UserErrors::InternalServerError)? - .pop() - .ok_or(report!(UserErrors::InternalServerError)) - } - pub async fn insert_in_v2(self, state: &SessionState) -> UserResult { let entity = self.entity.clone(); - let new_v2_role = self.convert_to_new_v2_role(EntityInfo { - org_id: entity.org_id.clone(), - merchant_id: None, - profile_id: None, - entity_id: entity.org_id.get_string_repr().to_owned(), - entity_type: EntityType::Organization, - }); - state - .store - .insert_user_role(InsertUserRolePayload::OnlyV2(new_v2_role)) - .await - .change_context(UserErrors::InternalServerError)? - .pop() - .ok_or(report!(UserErrors::InternalServerError)) - } - - pub async fn insert_in_v1_and_v2(self, state: &SessionState) -> UserResult { - let entity = self.entity.clone(); - - let new_v1_role = self - .clone() - .convert_to_new_v1_role(entity.org_id.clone(), entity.merchant_id.clone()); + let new_v2_role = self.convert_to_new_v2_role(entity.into()); - let new_v2_role = self.clone().convert_to_new_v2_role(EntityInfo { - org_id: entity.org_id.clone(), - merchant_id: None, - profile_id: None, - entity_id: entity.org_id.get_string_repr().to_owned(), - entity_type: EntityType::Organization, - }); - - Self::insert_v1_and_v2_in_db_and_get_v2(state, new_v1_role, new_v2_role).await - } -} - -impl NewUserRole { - pub async fn insert_in_v1_and_v2(self, state: &SessionState) -> UserResult { - let entity = self.entity.clone(); - - let new_v1_role = self - .clone() - .convert_to_new_v1_role(entity.org_id.clone(), entity.merchant_id.clone()); - - let new_v2_role = self.clone().convert_to_new_v2_role(EntityInfo { - org_id: entity.org_id.clone(), - merchant_id: Some(entity.merchant_id.clone()), - profile_id: None, - entity_id: entity.merchant_id.get_string_repr().to_owned(), - entity_type: EntityType::Merchant, - }); - - Self::insert_v1_and_v2_in_db_and_get_v2(state, new_v1_role, new_v2_role).await - } -} - -impl NewUserRole { - pub async fn insert_in_v2(self, state: &SessionState) -> UserResult { - let entity = self.entity.clone(); - - let new_v2_role = self.convert_to_new_v2_role(EntityInfo { - org_id: entity.org_id.clone(), - merchant_id: Some(entity.merchant_id.clone()), - profile_id: Some(entity.profile_id.clone()), - entity_id: entity.profile_id.get_string_repr().to_owned(), - entity_type: EntityType::Profile, - }); state - .store - .insert_user_role(InsertUserRolePayload::OnlyV2(new_v2_role)) + .global_store + .insert_user_role(new_v2_role) .await - .change_context(UserErrors::InternalServerError)? - .pop() - .ok_or(report!(UserErrors::InternalServerError)) + .change_context(UserErrors::InternalServerError) } } diff --git a/crates/router/src/types/domain/user/decision_manager.rs b/crates/router/src/types/domain/user/decision_manager.rs index 3fc05285ea89..bf5556dd9a75 100644 --- a/crates/router/src/types/domain/user/decision_manager.rs +++ b/crates/router/src/types/domain/user/decision_manager.rs @@ -1,6 +1,7 @@ use common_enums::TokenPurpose; +use common_utils::id_type; use diesel_models::{enums::UserStatus, user_role::UserRole}; -use error_stack::{report, ResultExt}; +use error_stack::ResultExt; use masking::Secret; use super::UserFromStorage; @@ -24,9 +25,10 @@ impl UserFlow { user: &UserFromStorage, path: &[TokenPurpose], state: &SessionState, + user_tenant_id: &id_type::TenantId, ) -> UserResult { match self { - Self::SPTFlow(flow) => flow.is_required(user, path, state).await, + Self::SPTFlow(flow) => flow.is_required(user, path, state, user_tenant_id).await, Self::JWTFlow(flow) => flow.is_required(user, state).await, } } @@ -50,6 +52,7 @@ impl SPTFlow { user: &UserFromStorage, path: &[TokenPurpose], state: &SessionState, + user_tenant_id: &id_type::TenantId, ) -> UserResult { match self { // Auth @@ -65,9 +68,10 @@ impl SPTFlow { .is_password_rotate_required(state) .map(|rotate_required| rotate_required && !path.contains(&TokenPurpose::SSO)), Self::MerchantSelect => Ok(state - .store + .global_store .list_user_roles_by_user_id(ListUserRolesByUserIdPayload { user_id: user.get_user_id(), + tenant_id: user_tenant_id, org_id: None, merchant_id: None, profile_id: None, @@ -93,6 +97,7 @@ impl SPTFlow { next_flow.origin.clone(), &state.conf, next_flow.path.to_vec(), + Some(state.tenant.tenant_id.clone()), ) .await .map(|token| token.into()) @@ -119,19 +124,20 @@ impl JWTFlow { next_flow: &NextFlow, user_role: &UserRole, ) -> UserResult> { - let (merchant_id, profile_id) = - utils::user_role::get_single_merchant_id_and_profile_id(state, user_role).await?; + let org_id = utils::user_role::get_single_org_id(state, user_role).await?; + let merchant_id = + utils::user_role::get_single_merchant_id(state, user_role, &org_id).await?; + let profile_id = + utils::user_role::get_single_profile_id(state, user_role, &merchant_id).await?; + auth::AuthToken::new_token( next_flow.user.get_user_id().to_string(), merchant_id, user_role.role_id.clone(), &state.conf, - user_role - .org_id - .clone() - .ok_or(report!(UserErrors::InternalServerError)) - .attach_printable("org_id not found")?, - Some(profile_id), + org_id, + profile_id, + Some(user_role.tenant_id.clone()), ) .await .map(|token| token.into()) @@ -218,6 +224,7 @@ pub struct CurrentFlow { origin: Origin, current_flow_index: usize, path: Vec, + tenant_id: Option, } impl CurrentFlow { @@ -237,6 +244,7 @@ impl CurrentFlow { origin: token.origin, current_flow_index: index, path, + tenant_id: token.tenant_id, }) } @@ -245,12 +253,21 @@ impl CurrentFlow { let remaining_flows = flows.iter().skip(self.current_flow_index + 1); for flow in remaining_flows { - if flow.is_required(&user, &self.path, state).await? { + if flow + .is_required( + &user, + &self.path, + state, + self.tenant_id.as_ref().unwrap_or(&state.tenant.tenant_id), + ) + .await? + { return Ok(NextFlow { origin: self.origin.clone(), next_flow: *flow, user, path: self.path, + tenant_id: self.tenant_id, }); } } @@ -263,6 +280,7 @@ pub struct NextFlow { next_flow: UserFlow, user: UserFromStorage, path: Vec, + tenant_id: Option, } impl NextFlow { @@ -274,12 +292,16 @@ impl NextFlow { let flows = origin.get_flows(); let path = vec![]; for flow in flows { - if flow.is_required(&user, &path, state).await? { + if flow + .is_required(&user, &path, state, &state.tenant.tenant_id) + .await? + { return Ok(Self { origin, next_flow: *flow, user, path, + tenant_id: Some(state.tenant.tenant_id.clone()), }); } } @@ -299,9 +321,10 @@ impl NextFlow { self.user.get_verification_days_left(state)?; } let user_role = state - .store + .global_store .list_user_roles_by_user_id(ListUserRolesByUserIdPayload { user_id: self.user.get_user_id(), + tenant_id: self.tenant_id.as_ref().unwrap_or(&state.tenant.tenant_id), org_id: None, merchant_id: None, profile_id: None, @@ -314,8 +337,7 @@ impl NextFlow { .change_context(UserErrors::InternalServerError)? .pop() .ok_or(UserErrors::InternalServerError)?; - utils::user_role::set_role_permissions_in_cache_by_user_role(state, &user_role) - .await; + utils::user_role::set_role_info_in_cache_by_user_role(state, &user_role).await; jwt_flow.generate_jwt(state, self, &user_role).await } @@ -334,8 +356,7 @@ impl NextFlow { { self.user.get_verification_days_left(state)?; } - utils::user_role::set_role_permissions_in_cache_by_user_role(state, user_role) - .await; + utils::user_role::set_role_info_in_cache_by_user_role(state, user_role).await; jwt_flow.generate_jwt(state, self, user_role).await } @@ -350,12 +371,16 @@ impl NextFlow { .ok_or(UserErrors::InternalServerError)?; let remaining_flows = flows.iter().skip(index + 1); for flow in remaining_flows { - if flow.is_required(&user, &self.path, state).await? { + if flow + .is_required(&user, &self.path, state, &state.tenant.tenant_id) + .await? + { return Ok(Self { origin: self.origin.clone(), next_flow: *flow, user, path: self.path, + tenant_id: Some(state.tenant.tenant_id.clone()), }); } } diff --git a/crates/router/src/types/payment_methods.rs b/crates/router/src/types/payment_methods.rs index a1c0f8156fa8..c40d6fedfe73 100644 --- a/crates/router/src/types/payment_methods.rs +++ b/crates/router/src/types/payment_methods.rs @@ -10,32 +10,17 @@ use crate::{ }; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -#[async_trait::async_trait] pub trait VaultingInterface { fn get_vaulting_request_url() -> &'static str; + + fn get_vaulting_flow_name() -> &'static str; } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -#[async_trait::async_trait] pub trait VaultingDataInterface { fn get_vaulting_data_key(&self) -> String; } -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -#[derive(Debug, serde::Deserialize, serde::Serialize)] -pub struct VaultId(String); - -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -impl VaultId { - pub fn get_string_repr(&self) -> &String { - &self.0 - } - - pub fn generate(id: String) -> Self { - Self(id) - } -} - #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct VaultFingerprintRequest { @@ -53,7 +38,7 @@ pub struct VaultFingerprintResponse { #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct AddVaultRequest { pub entity_id: common_utils::id_type::MerchantId, - pub vault_id: VaultId, + pub vault_id: domain::VaultId, pub data: D, pub ttl: i64, } @@ -62,7 +47,7 @@ pub struct AddVaultRequest { #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct AddVaultResponse { pub entity_id: common_utils::id_type::MerchantId, - pub vault_id: VaultId, + pub vault_id: domain::VaultId, pub fingerprint_id: Option, } @@ -79,27 +64,51 @@ pub struct GetVaultFingerprint; pub struct VaultRetrieve; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -#[async_trait::async_trait] +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct VaultDelete; + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] impl VaultingInterface for AddVault { fn get_vaulting_request_url() -> &'static str { consts::ADD_VAULT_REQUEST_URL } + + fn get_vaulting_flow_name() -> &'static str { + consts::VAULT_ADD_FLOW_TYPE + } } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -#[async_trait::async_trait] impl VaultingInterface for GetVaultFingerprint { fn get_vaulting_request_url() -> &'static str { consts::VAULT_FINGERPRINT_REQUEST_URL } + + fn get_vaulting_flow_name() -> &'static str { + consts::VAULT_GET_FINGERPRINT_FLOW_TYPE + } } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -#[async_trait::async_trait] impl VaultingInterface for VaultRetrieve { fn get_vaulting_request_url() -> &'static str { consts::VAULT_RETRIEVE_REQUEST_URL } + + fn get_vaulting_flow_name() -> &'static str { + consts::VAULT_RETRIEVE_FLOW_TYPE + } +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl VaultingInterface for VaultDelete { + fn get_vaulting_request_url() -> &'static str { + consts::VAULT_DELETE_REQUEST_URL + } + + fn get_vaulting_flow_name() -> &'static str { + consts::VAULT_DELETE_FLOW_TYPE + } } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] @@ -109,7 +118,6 @@ pub enum PaymentMethodVaultingData { } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -#[async_trait::async_trait] impl VaultingDataInterface for PaymentMethodVaultingData { fn get_vaulting_data_key(&self) -> String { match &self { @@ -118,21 +126,11 @@ impl VaultingDataInterface for PaymentMethodVaultingData { } } -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -pub struct PaymentMethodClientSecret; - -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -impl PaymentMethodClientSecret { - pub fn generate(payment_method_id: &common_utils::id_type::GlobalPaymentMethodId) -> String { - todo!() - } -} - #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] pub struct SavedPMLPaymentsInfo { pub payment_intent: storage::PaymentIntent, - pub business_profile: Option, - pub requires_cvv: bool, + pub profile: domain::Profile, + pub collect_cvv_during_payment: bool, pub off_session_payment_flag: bool, pub is_connector_agnostic_mit_enabled: bool, } @@ -141,11 +139,25 @@ pub struct SavedPMLPaymentsInfo { #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct VaultRetrieveRequest { pub entity_id: common_utils::id_type::MerchantId, - pub vault_id: VaultId, + pub vault_id: domain::VaultId, } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct VaultRetrieveResponse { - pub data: Secret, + pub data: PaymentMethodVaultingData, +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct VaultDeleteRequest { + pub entity_id: common_utils::id_type::MerchantId, + pub vault_id: domain::VaultId, +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct VaultDeleteResponse { + pub entity_id: common_utils::id_type::MerchantId, + pub vault_id: domain::VaultId, } diff --git a/crates/router/src/types/storage.rs b/crates/router/src/types/storage.rs index 13a5291062c4..24573548d797 100644 --- a/crates/router/src/types/storage.rs +++ b/crates/router/src/types/storage.rs @@ -12,6 +12,7 @@ pub mod configs; pub mod customers; pub mod dashboard_metadata; pub mod dispute; +pub mod dynamic_routing_stats; pub mod enums; pub mod ephemeral_key; pub mod events; @@ -46,8 +47,10 @@ pub use diesel_models::{ process_tracker::business_status, ProcessTracker, ProcessTrackerNew, ProcessTrackerRunner, ProcessTrackerUpdate, }; +#[cfg(feature = "v1")] +pub use hyperswitch_domain_models::payments::payment_attempt::PaymentAttemptNew; pub use hyperswitch_domain_models::payments::{ - payment_attempt::{PaymentAttempt, PaymentAttemptNew, PaymentAttemptUpdate}, + payment_attempt::{PaymentAttempt, PaymentAttemptUpdate}, payment_intent::{PaymentIntentUpdate, PaymentIntentUpdateFields}, PaymentIntent, }; @@ -61,12 +64,12 @@ pub use scheduler::db::process_tracker; pub use self::{ address::*, api_keys::*, authentication::*, authorization::*, blocklist::*, blocklist_fingerprint::*, blocklist_lookup::*, business_profile::*, capture::*, cards_info::*, - configs::*, customers::*, dashboard_metadata::*, dispute::*, ephemeral_key::*, events::*, - file::*, fraud_check::*, generic_link::*, gsm::*, locker_mock_up::*, mandate::*, - merchant_account::*, merchant_connector_account::*, merchant_key_store::*, payment_link::*, - payment_method::*, process_tracker::*, refund::*, reverse_lookup::*, role::*, - routing_algorithm::*, unified_translations::*, user::*, user_authentication_method::*, - user_role::*, + configs::*, customers::*, dashboard_metadata::*, dispute::*, dynamic_routing_stats::*, + ephemeral_key::*, events::*, file::*, fraud_check::*, generic_link::*, gsm::*, + locker_mock_up::*, mandate::*, merchant_account::*, merchant_connector_account::*, + merchant_key_store::*, payment_link::*, payment_method::*, process_tracker::*, refund::*, + reverse_lookup::*, role::*, routing_algorithm::*, unified_translations::*, user::*, + user_authentication_method::*, user_role::*, }; use crate::types::api::routing; diff --git a/crates/router/src/types/storage/dispute.rs b/crates/router/src/types/storage/dispute.rs index 28a9573b3330..cf1ff52960fd 100644 --- a/crates/router/src/types/storage/dispute.rs +++ b/crates/router/src/types/storage/dispute.rs @@ -1,6 +1,6 @@ use async_bb8_diesel::AsyncRunQueryDsl; use common_utils::errors::CustomResult; -use diesel::{associations::HasTable, ExpressionMethods, QueryDsl}; +use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods, QueryDsl}; pub use diesel_models::dispute::{Dispute, DisputeNew, DisputeUpdate}; use diesel_models::{errors, query::generics::db_metrics, schema::dispute::dsl}; use error_stack::ResultExt; @@ -43,9 +43,11 @@ impl DisputeDbExt for Dispute { &dispute_list_constraints.dispute_id, ) { search_by_payment_or_dispute_id = true; - filter = filter - .filter(dsl::payment_id.eq(payment_id.to_owned())) - .or_filter(dsl::dispute_id.eq(dispute_id.to_owned())); + filter = filter.filter( + dsl::payment_id + .eq(payment_id.to_owned()) + .or(dsl::dispute_id.eq(dispute_id.to_owned())), + ); }; if !search_by_payment_or_dispute_id { @@ -83,14 +85,8 @@ impl DisputeDbExt for Dispute { if let Some(dispute_status) = &dispute_list_constraints.dispute_status { filter = filter.filter(dsl::dispute_status.eq_any(dispute_status.clone())); } - if let Some(currency_list) = &dispute_list_constraints.currency { - let currency: Vec = currency_list - .iter() - .map(|currency| currency.to_string()) - .collect(); - - filter = filter.filter(dsl::currency.eq_any(currency)); + filter = filter.filter(dsl::dispute_currency.eq_any(currency_list.clone())); } if let Some(merchant_connector_id) = &dispute_list_constraints.merchant_connector_id { filter = filter.filter(dsl::merchant_connector_id.eq(merchant_connector_id.clone())) diff --git a/crates/router/src/types/storage/dynamic_routing_stats.rs b/crates/router/src/types/storage/dynamic_routing_stats.rs new file mode 100644 index 000000000000..ba692a252555 --- /dev/null +++ b/crates/router/src/types/storage/dynamic_routing_stats.rs @@ -0,0 +1 @@ +pub use diesel_models::dynamic_routing_stats::{DynamicRoutingStats, DynamicRoutingStatsNew}; diff --git a/crates/router/src/types/storage/ephemeral_key.rs b/crates/router/src/types/storage/ephemeral_key.rs index 9927e16b3fad..c4b8e2ba701a 100644 --- a/crates/router/src/types/storage/ephemeral_key.rs +++ b/crates/router/src/types/storage/ephemeral_key.rs @@ -1 +1,18 @@ pub use diesel_models::ephemeral_key::{EphemeralKey, EphemeralKeyNew}; +#[cfg(feature = "v2")] +pub use diesel_models::ephemeral_key::{EphemeralKeyType, EphemeralKeyTypeNew, ResourceType}; + +#[cfg(feature = "v2")] +use crate::types::transformers::ForeignFrom; +#[cfg(feature = "v2")] +impl ForeignFrom for api_models::ephemeral_key::EphemeralKeyResponse { + fn foreign_from(from: EphemeralKeyType) -> Self { + Self { + customer_id: from.customer_id, + created_at: from.created_at, + expires: from.expires, + secret: from.secret, + id: from.id, + } + } +} diff --git a/crates/router/src/types/storage/payment_attempt.rs b/crates/router/src/types/storage/payment_attempt.rs index f6caa64fe6e1..0291374d54f6 100644 --- a/crates/router/src/types/storage/payment_attempt.rs +++ b/crates/router/src/types/storage/payment_attempt.rs @@ -2,7 +2,7 @@ use common_utils::types::MinorUnit; use diesel_models::{capture::CaptureNew, enums}; use error_stack::ResultExt; pub use hyperswitch_domain_models::payments::payment_attempt::{ - PaymentAttempt, PaymentAttemptNew, PaymentAttemptUpdate, + PaymentAttempt, PaymentAttemptUpdate, }; use crate::{ @@ -63,6 +63,7 @@ impl PaymentAttemptExt for PaymentAttempt { capture_sequence, connector_capture_id: None, connector_response_reference_id: None, + connector_capture_data: None, }) } @@ -79,12 +80,14 @@ impl PaymentAttemptExt for PaymentAttempt { #[cfg(feature = "v1")] fn get_surcharge_details(&self) -> Option { - self.surcharge_amount.map(|surcharge_amount| { - api_models::payments::RequestSurchargeDetails { - surcharge_amount, - tax_amount: self.tax_amount, - } - }) + self.net_amount + .get_surcharge_amount() + .map( + |surcharge_amount| api_models::payments::RequestSurchargeDetails { + surcharge_amount, + tax_amount: self.net_amount.get_tax_on_surcharge(), + }, + ) } #[cfg(feature = "v2")] @@ -94,9 +97,7 @@ impl PaymentAttemptExt for PaymentAttempt { #[cfg(feature = "v1")] fn get_total_amount(&self) -> MinorUnit { - self.amount - + self.surcharge_amount.unwrap_or_default() - + self.tax_amount.unwrap_or_default() + self.net_amount.get_total_amount() } #[cfg(feature = "v2")] @@ -122,10 +123,10 @@ impl AttemptStatusExt for enums::AttemptStatus { ))] mod tests { #![allow(clippy::expect_used, clippy::unwrap_used, clippy::print_stderr)] + use hyperswitch_domain_models::payments::payment_attempt::PaymentAttemptNew; use tokio::sync::oneshot; use uuid::Uuid; - use super::*; use crate::{ configs::settings::Settings, db::StorageImpl, @@ -167,14 +168,11 @@ mod tests { merchant_id: Default::default(), attempt_id: Default::default(), status: Default::default(), - amount: Default::default(), net_amount: Default::default(), currency: Default::default(), save_to_locker: Default::default(), error_message: Default::default(), offer_amount: Default::default(), - surcharge_amount: Default::default(), - tax_amount: Default::default(), payment_method_id: Default::default(), payment_method: Default::default(), capture_method: Default::default(), @@ -218,13 +216,12 @@ mod tests { customer_acceptance: Default::default(), profile_id: common_utils::generate_profile_id_of_default_length(), organization_id: Default::default(), - shipping_cost: Default::default(), - order_tax_amount: Default::default(), + connector_mandate_detail: Default::default(), }; let store = state .stores - .get(state.conf.multitenancy.get_tenant_names().first().unwrap()) + .get(state.conf.multitenancy.get_tenant_ids().first().unwrap()) .unwrap(); let response = store .insert_payment_attempt(payment_attempt, enums::MerchantStorageScheme::PostgresOnly) @@ -255,14 +252,11 @@ mod tests { modified_at: current_time.into(), attempt_id: attempt_id.clone(), status: Default::default(), - amount: Default::default(), net_amount: Default::default(), currency: Default::default(), save_to_locker: Default::default(), error_message: Default::default(), offer_amount: Default::default(), - surcharge_amount: Default::default(), - tax_amount: Default::default(), payment_method_id: Default::default(), payment_method: Default::default(), capture_method: Default::default(), @@ -306,12 +300,11 @@ mod tests { customer_acceptance: Default::default(), profile_id: common_utils::generate_profile_id_of_default_length(), organization_id: Default::default(), - shipping_cost: Default::default(), - order_tax_amount: Default::default(), + connector_mandate_detail: Default::default(), }; let store = state .stores - .get(state.conf.multitenancy.get_tenant_names().first().unwrap()) + .get(state.conf.multitenancy.get_tenant_ids().first().unwrap()) .unwrap(); store .insert_payment_attempt(payment_attempt, enums::MerchantStorageScheme::PostgresOnly) @@ -357,14 +350,11 @@ mod tests { mandate_id: Some("man_121212".to_string()), attempt_id: uuid.clone(), status: Default::default(), - amount: Default::default(), net_amount: Default::default(), currency: Default::default(), save_to_locker: Default::default(), error_message: Default::default(), offer_amount: Default::default(), - surcharge_amount: Default::default(), - tax_amount: Default::default(), payment_method_id: Default::default(), payment_method: Default::default(), capture_method: Default::default(), @@ -407,12 +397,11 @@ mod tests { customer_acceptance: Default::default(), profile_id: common_utils::generate_profile_id_of_default_length(), organization_id: Default::default(), - shipping_cost: Default::default(), - order_tax_amount: Default::default(), + connector_mandate_detail: Default::default(), }; let store = state .stores - .get(state.conf.multitenancy.get_tenant_names().first().unwrap()) + .get(state.conf.multitenancy.get_tenant_ids().first().unwrap()) .unwrap(); store .insert_payment_attempt(payment_attempt, enums::MerchantStorageScheme::PostgresOnly) diff --git a/crates/router/src/types/storage/payment_link.rs b/crates/router/src/types/storage/payment_link.rs index 9a251d760bd6..ae9aa4173266 100644 --- a/crates/router/src/types/storage/payment_link.rs +++ b/crates/router/src/types/storage/payment_link.rs @@ -11,8 +11,8 @@ use crate::{ core::errors::{self, CustomResult}, logger, }; -#[async_trait::async_trait] +#[async_trait::async_trait] pub trait PaymentLinkDbExt: Sized { async fn filter_by_constraints( conn: &PgPooledConn, diff --git a/crates/router/src/types/storage/payment_method.rs b/crates/router/src/types/storage/payment_method.rs index bb1b801edb6f..bc5f6651b6b5 100644 --- a/crates/router/src/types/storage/payment_method.rs +++ b/crates/router/src/types/storage/payment_method.rs @@ -1,8 +1,3 @@ -use std::{ - collections::HashMap, - ops::{Deref, DerefMut}, -}; - use api_models::payment_methods; use diesel_models::enums; pub use diesel_models::payment_method::{ @@ -110,6 +105,10 @@ impl PaymentTokenData { } } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct PaymentMethodListContext { pub card_details: Option, @@ -118,32 +117,35 @@ pub struct PaymentMethodListContext { pub bank_transfer_details: Option, } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct PaymentsMandateReferenceRecord { - pub connector_mandate_id: String, - pub payment_method_type: Option, - pub original_payment_authorized_amount: Option, - pub original_payment_authorized_currency: Option, - pub mandate_metadata: Option, -} - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct PaymentsMandateReference( - pub HashMap, -); - -impl Deref for PaymentsMandateReference { - type Target = - HashMap; - - fn deref(&self) -> &Self::Target { - &self.0 - } +pub enum PaymentMethodListContext { + Card { + card_details: api::CardDetailFromLocker, + token_data: Option, + }, + Bank { + token_data: Option, + }, + #[cfg(feature = "payouts")] + BankTransfer { + bank_transfer_details: api::BankPayout, + token_data: Option, + }, + TemporaryToken { + token_data: Option, + }, } -impl DerefMut for PaymentsMandateReference { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl PaymentMethodListContext { + pub(crate) fn get_token_data(&self) -> Option { + match self { + Self::Card { token_data, .. } + | Self::Bank { token_data } + | Self::BankTransfer { token_data, .. } + | Self::TemporaryToken { token_data } => token_data.clone(), + } } } diff --git a/crates/router/src/types/storage/refund.rs b/crates/router/src/types/storage/refund.rs index 8076c2c7a6e1..e46fc1a3f476 100644 --- a/crates/router/src/types/storage/refund.rs +++ b/crates/router/src/types/storage/refund.rs @@ -1,7 +1,7 @@ use api_models::payments::AmountFilter; use async_bb8_diesel::AsyncRunQueryDsl; use common_utils::errors::CustomResult; -use diesel::{associations::HasTable, ExpressionMethods, QueryDsl}; +use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods, QueryDsl}; pub use diesel_models::refund::{ Refund, RefundCoreWorkflow, RefundNew, RefundUpdate, RefundUpdateInternal, }; @@ -67,8 +67,11 @@ impl RefundDbExt for Refund { ) { search_by_pay_or_ref_id = true; filter = filter - .filter(dsl::payment_id.eq(pid.to_owned())) - .or_filter(dsl::refund_id.eq(ref_id.to_owned())) + .filter( + dsl::payment_id + .eq(pid.to_owned()) + .or(dsl::refund_id.eq(ref_id.to_owned())), + ) .limit(limit) .offset(offset); }; @@ -228,9 +231,11 @@ impl RefundDbExt for Refund { &refund_list_details.refund_id, ) { search_by_pay_or_ref_id = true; - filter = filter - .filter(dsl::payment_id.eq(pid.to_owned())) - .or_filter(dsl::refund_id.eq(ref_id.to_owned())); + filter = filter.filter( + dsl::payment_id + .eq(pid.to_owned()) + .or(dsl::refund_id.eq(ref_id.to_owned())), + ); }; if !search_by_pay_or_ref_id { diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index b032906293be..bbcdfc535df8 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -10,6 +10,7 @@ use common_utils::{ ext_traits::{Encode, StringExt, ValueExt}, fp_utils::when, pii, + types::ConnectorTransactionIdTrait, }; use diesel_models::enums as storage_enums; use error_stack::{report, ResultExt}; @@ -100,8 +101,8 @@ impl merchant_id: item.merchant_id.to_owned(), customer_id: Some(item.customer_id.to_owned()), payment_method_id: item.get_id().clone(), - payment_method: item.payment_method, - payment_method_type: item.payment_method_type, + payment_method: item.get_payment_method_type(), + payment_method_type: item.get_payment_method_subtype(), card: card_details, recurring_enabled: false, installment_payment_enabled: false, @@ -133,43 +134,10 @@ impl } } +// TODO: remove this usage in v1 code impl ForeignFrom for storage_enums::IntentStatus { fn foreign_from(s: storage_enums::AttemptStatus) -> Self { - match s { - storage_enums::AttemptStatus::Charged | storage_enums::AttemptStatus::AutoRefunded => { - Self::Succeeded - } - - storage_enums::AttemptStatus::ConfirmationAwaited => Self::RequiresConfirmation, - storage_enums::AttemptStatus::PaymentMethodAwaited => Self::RequiresPaymentMethod, - - storage_enums::AttemptStatus::Authorized => Self::RequiresCapture, - storage_enums::AttemptStatus::AuthenticationPending - | storage_enums::AttemptStatus::DeviceDataCollectionPending => { - Self::RequiresCustomerAction - } - storage_enums::AttemptStatus::Unresolved => Self::RequiresMerchantAction, - - storage_enums::AttemptStatus::PartialCharged => Self::PartiallyCaptured, - storage_enums::AttemptStatus::PartialChargedAndChargeable => { - Self::PartiallyCapturedAndCapturable - } - storage_enums::AttemptStatus::Started - | storage_enums::AttemptStatus::AuthenticationSuccessful - | storage_enums::AttemptStatus::Authorizing - | storage_enums::AttemptStatus::CodInitiated - | storage_enums::AttemptStatus::VoidInitiated - | storage_enums::AttemptStatus::CaptureInitiated - | storage_enums::AttemptStatus::Pending => Self::Processing, - - storage_enums::AttemptStatus::AuthenticationFailed - | storage_enums::AttemptStatus::AuthorizationFailed - | storage_enums::AttemptStatus::VoidFailed - | storage_enums::AttemptStatus::RouterDeclined - | storage_enums::AttemptStatus::CaptureFailed - | storage_enums::AttemptStatus::Failure => Self::Failed, - storage_enums::AttemptStatus::Voided => Self::Cancelled, - } + Self::from(s) } } @@ -246,6 +214,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { api_enums::Connector::Adyen => Self::Adyen, api_enums::Connector::Adyenplatform => Self::Adyenplatform, api_enums::Connector::Airwallex => Self::Airwallex, + // api_enums::Connector::Amazonpay => Self::Amazonpay, api_enums::Connector::Authorizedotnet => Self::Authorizedotnet, api_enums::Connector::Bambora => Self::Bambora, api_enums::Connector::Bamboraapac => Self::Bamboraapac, @@ -259,11 +228,18 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { api_enums::Connector::Checkout => Self::Checkout, api_enums::Connector::Coinbase => Self::Coinbase, api_enums::Connector::Cryptopay => Self::Cryptopay, + api_enums::Connector::CtpMastercard => { + Err(common_utils::errors::ValidationError::InvalidValue { + message: "ctp mastercard is not a routable connector".to_string(), + })? + } api_enums::Connector::Cybersource => Self::Cybersource, api_enums::Connector::Datatrans => Self::Datatrans, api_enums::Connector::Deutschebank => Self::Deutschebank, + api_enums::Connector::Digitalvirgo => Self::Digitalvirgo, api_enums::Connector::Dlocal => Self::Dlocal, api_enums::Connector::Ebanx => Self::Ebanx, + api_enums::Connector::Elavon => Self::Elavon, api_enums::Connector::Fiserv => Self::Fiserv, api_enums::Connector::Fiservemea => Self::Fiservemea, api_enums::Connector::Fiuu => Self::Fiuu, @@ -278,7 +254,9 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { } api_enums::Connector::Helcim => Self::Helcim, api_enums::Connector::Iatapay => Self::Iatapay, + // api_enums::Connector::Inespay => Self::Inespay, api_enums::Connector::Itaubank => Self::Itaubank, + api_enums::Connector::Jpmorgan => Self::Jpmorgan, api_enums::Connector::Klarna => Self::Klarna, api_enums::Connector::Mifinity => Self::Mifinity, api_enums::Connector::Mollie => Self::Mollie, @@ -289,8 +267,9 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { })? } api_enums::Connector::Nexinets => Self::Nexinets, - // api_enums::Connector::Nexixpay => Self::Nexixpay, + api_enums::Connector::Nexixpay => Self::Nexixpay, api_enums::Connector::Nmi => Self::Nmi, + // api_enums::Connector::Nomupay => Self::Nomupay, api_enums::Connector::Noon => Self::Noon, api_enums::Connector::Novalnet => Self::Novalnet, api_enums::Connector::Nuvei => Self::Nuvei, @@ -306,6 +285,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { api_enums::Connector::Prophetpay => Self::Prophetpay, api_enums::Connector::Rapyd => Self::Rapyd, api_enums::Connector::Razorpay => Self::Razorpay, + // api_enums::Connector::Redsys => Self::Redsys, api_enums::Connector::Shift4 => Self::Shift4, api_enums::Connector::Signifyd => { Err(common_utils::errors::ValidationError::InvalidValue { @@ -324,12 +304,16 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { // api_enums::Connector::Thunes => Self::Thunes, api_enums::Connector::Trustpay => Self::Trustpay, api_enums::Connector::Tsys => Self::Tsys, + // api_enums::Connector::UnifiedAuthenticationService => { + // Self::UnifiedAuthenticationService + // } api_enums::Connector::Volt => Self::Volt, api_enums::Connector::Wellsfargo => Self::Wellsfargo, // api_enums::Connector::Wellsfargopayout => Self::Wellsfargopayout, api_enums::Connector::Wise => Self::Wise, api_enums::Connector::Worldline => Self::Worldline, api_enums::Connector::Worldpay => Self::Worldpay, + // api_enums::Connector::Xendit => Self::Xendit, api_enums::Connector::Zen => Self::Zen, api_enums::Connector::Zsl => Self::Zsl, #[cfg(feature = "dummy_connector")] @@ -476,6 +460,7 @@ impl ForeignFrom for api_enums::PaymentMethod { | api_enums::PaymentMethodType::Dana | api_enums::PaymentMethodType::MbWay | api_enums::PaymentMethodType::MobilePay + | api_enums::PaymentMethodType::Paze | api_enums::PaymentMethodType::SamsungPay | api_enums::PaymentMethodType::Twint | api_enums::PaymentMethodType::Vipps @@ -564,6 +549,7 @@ impl ForeignFrom for api_enums::PaymentMethod { | api_enums::PaymentMethodType::DuitNow | api_enums::PaymentMethodType::PromptPay | api_enums::PaymentMethodType::VietQr => Self::RealTimePayment, + api_enums::PaymentMethodType::DirectCarrierBilling => Self::MobilePayment, } } } @@ -590,6 +576,7 @@ impl ForeignTryFrom for api_enums::PaymentMethod { payments::PaymentMethodData::GiftCard(..) => Ok(Self::GiftCard), payments::PaymentMethodData::CardRedirect(..) => Ok(Self::CardRedirect), payments::PaymentMethodData::OpenBanking(..) => Ok(Self::OpenBanking), + payments::PaymentMethodData::MobilePayment(..) => Ok(Self::MobilePayment), payments::PaymentMethodData::MandatePayment => { Err(errors::ApiErrorResponse::InvalidRequestData { message: ("Mandate payments cannot have payment_method_data field".to_string()), @@ -722,7 +709,7 @@ impl ForeignFrom for api_types::Config { } } -impl<'a> ForeignFrom<&'a api_types::ConfigUpdate> for storage::ConfigUpdate { +impl ForeignFrom<&api_types::ConfigUpdate> for storage::ConfigUpdate { fn foreign_from(config: &api_types::ConfigUpdate) -> Self { Self::Update { config: Some(config.value.clone()), @@ -730,7 +717,7 @@ impl<'a> ForeignFrom<&'a api_types::ConfigUpdate> for storage::ConfigUpdate { } } -impl<'a> From<&'a domain::Address> for api_types::Address { +impl From<&domain::Address> for hyperswitch_domain_models::address::Address { fn from(address: &domain::Address) -> Self { // If all the fields of address are none, then pass the address as None let address_details = if address.city.is_none() @@ -745,7 +732,7 @@ impl<'a> From<&'a domain::Address> for api_types::Address { { None } else { - Some(api_types::AddressDetails { + Some(hyperswitch_domain_models::address::AddressDetails { city: address.city.clone(), country: address.country, line1: address.line1.clone().map(Encryptable::into_inner), @@ -762,7 +749,7 @@ impl<'a> From<&'a domain::Address> for api_types::Address { let phone_details = if address.phone_number.is_none() && address.country_code.is_none() { None } else { - Some(api_types::PhoneDetails { + Some(hyperswitch_domain_models::address::PhoneDetails { number: address.phone_number.clone().map(Encryptable::into_inner), country_code: address.country_code.clone(), }) @@ -910,7 +897,13 @@ impl ForeignFrom for api_models::disputes::DisputeResponse { payment_id: dispute.payment_id, attempt_id: dispute.attempt_id, amount: dispute.amount, - currency: dispute.currency, + currency: dispute.dispute_currency.unwrap_or( + dispute + .currency + .to_uppercase() + .parse_enum("Currency") + .unwrap_or_default(), + ), dispute_stage: dispute.dispute_stage, dispute_status: dispute.dispute_status, connector: dispute.connector, @@ -1007,6 +1000,7 @@ impl ForeignTryFrom { type Error = error_stack::Report; fn foreign_try_from(item: domain::MerchantConnectorAccount) -> Result { + #[cfg(feature = "v1")] let payment_methods_enabled = match item.payment_methods_enabled { Some(secret_val) => { let val = secret_val @@ -1019,6 +1013,7 @@ impl ForeignTryFrom } None => None, }; + let frm_configs = match item.frm_configs { Some(frm_value) => { let configs_for_frm : Vec = frm_value @@ -1046,7 +1041,6 @@ impl ForeignTryFrom test_mode: item.test_mode, disabled: item.disabled, payment_methods_enabled, - metadata: item.metadata, business_country: item.business_country, business_label: item.business_label, business_sub_label: item.business_sub_label, @@ -1055,31 +1049,6 @@ impl ForeignTryFrom applepay_verified_domains: item.applepay_verified_domains, pm_auth_config: item.pm_auth_config, status: item.status, - additional_merchant_data: item - .additional_merchant_data - .map(|data| { - let data = data.into_inner(); - serde_json::Value::parse_value::( - data.expose(), - "AdditionalMerchantData", - ) - .attach_printable("Unable to deserialize additional_merchant_data") - .change_context(errors::ApiErrorResponse::InternalServerError) - }) - .transpose()? - .map(api_models::admin::AdditionalMerchantData::foreign_from), - connector_wallets_details: item - .connector_wallets_details - .map(|data| { - data.into_inner() - .expose() - .parse_value::( - "ConnectorWalletDetails", - ) - .attach_printable("Unable to deserialize connector_wallets_details") - .change_context(errors::ApiErrorResponse::InternalServerError) - }) - .transpose()?, }; #[cfg(feature = "v2")] let response = Self { @@ -1088,43 +1057,18 @@ impl ForeignTryFrom connector_name: item.connector_name, connector_label: item.connector_label, disabled: item.disabled, - payment_methods_enabled, - metadata: item.metadata, + payment_methods_enabled: item.payment_methods_enabled, frm_configs, profile_id: item.profile_id, applepay_verified_domains: item.applepay_verified_domains, pm_auth_config: item.pm_auth_config, status: item.status, - additional_merchant_data: item - .additional_merchant_data - .map(|data| { - let data = data.into_inner(); - serde_json::Value::parse_value::( - data.expose(), - "AdditionalMerchantData", - ) - .attach_printable("Unable to deserialize additional_merchant_data") - .change_context(errors::ApiErrorResponse::InternalServerError) - }) - .transpose()? - .map(api_models::admin::AdditionalMerchantData::foreign_from), - connector_wallets_details: item - .connector_wallets_details - .map(|data| { - data.into_inner() - .expose() - .parse_value::( - "ConnectorWalletDetails", - ) - .attach_printable("Unable to deserialize connector_wallets_details") - .change_context(errors::ApiErrorResponse::InternalServerError) - }) - .transpose()?, }; Ok(response) } } +#[cfg(feature = "v1")] impl ForeignTryFrom for api_models::admin::MerchantConnectorResponse { @@ -1288,18 +1232,119 @@ impl ForeignTryFrom } } +#[cfg(feature = "v2")] +impl ForeignTryFrom + for api_models::admin::MerchantConnectorResponse +{ + type Error = error_stack::Report; + fn foreign_try_from(item: domain::MerchantConnectorAccount) -> Result { + let frm_configs = match item.frm_configs { + Some(ref frm_value) => { + let configs_for_frm : Vec = frm_value + .iter() + .map(|config| { config + .peek() + .clone() + .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(), + }) + }) + .collect::, _>>()?; + Some(configs_for_frm) + } + None => None, + }; + + // parse the connector_account_details into ConnectorAuthType + let connector_account_details: hyperswitch_domain_models::router_data::ConnectorAuthType = + item.connector_account_details + .clone() + .into_inner() + .parse_value("ConnectorAuthType") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while parsing value for ConnectorAuthType")?; + // get the masked keys from the ConnectorAuthType and encode it to secret value + let masked_connector_account_details = Secret::new( + connector_account_details + .get_masked_keys() + .encode_to_value() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to encode ConnectorAuthType")?, + ); + + let response = Self { + id: item.get_id(), + connector_type: item.connector_type, + connector_name: item.connector_name, + connector_label: item.connector_label, + connector_account_details: masked_connector_account_details, + disabled: item.disabled, + payment_methods_enabled: item.payment_methods_enabled, + metadata: item.metadata, + frm_configs, + connector_webhook_details: item + .connector_webhook_details + .map(|webhook_details| { + serde_json::Value::parse_value( + webhook_details.expose(), + "MerchantConnectorWebhookDetails", + ) + .attach_printable("Unable to deserialize connector_webhook_details") + .change_context(errors::ApiErrorResponse::InternalServerError) + }) + .transpose()?, + profile_id: item.profile_id, + applepay_verified_domains: item.applepay_verified_domains, + pm_auth_config: item.pm_auth_config, + status: item.status, + additional_merchant_data: item + .additional_merchant_data + .map(|data| { + let data = data.into_inner(); + serde_json::Value::parse_value::( + data.expose(), + "AdditionalMerchantData", + ) + .attach_printable("Unable to deserialize additional_merchant_data") + .change_context(errors::ApiErrorResponse::InternalServerError) + }) + .transpose()? + .map(api_models::admin::AdditionalMerchantData::foreign_from), + connector_wallets_details: item + .connector_wallets_details + .map(|data| { + data.into_inner() + .expose() + .parse_value::( + "ConnectorWalletDetails", + ) + .attach_printable("Unable to deserialize connector_wallets_details") + .change_context(errors::ApiErrorResponse::InternalServerError) + }) + .transpose()?, + }; + Ok(response) + } +} + #[cfg(feature = "v1")] impl ForeignFrom for payments::PaymentAttemptResponse { fn foreign_from(payment_attempt: storage::PaymentAttempt) -> Self { + let connector_transaction_id = payment_attempt + .get_connector_payment_id() + .map(ToString::to_string); Self { attempt_id: payment_attempt.attempt_id, status: payment_attempt.status, - amount: payment_attempt.amount, + amount: payment_attempt.net_amount.get_order_amount(), + order_tax_amount: payment_attempt.net_amount.get_order_tax_amount(), currency: payment_attempt.currency, connector: payment_attempt.connector, error_message: payment_attempt.error_reason, payment_method: payment_attempt.payment_method, - connector_transaction_id: payment_attempt.connector_transaction_id, + connector_transaction_id, capture_method: payment_attempt.capture_method, authentication_type: payment_attempt.authentication_type, created_at: payment_attempt.created_at, @@ -1322,6 +1367,7 @@ impl ForeignFrom for payments::PaymentAttemptResponse { impl ForeignFrom for payments::CaptureResponse { fn foreign_from(capture: storage::Capture) -> Self { + let connector_capture_id = capture.get_optional_connector_transaction_id().cloned(); Self { capture_id: capture.capture_id, status: capture.status, @@ -1329,7 +1375,7 @@ impl ForeignFrom for payments::CaptureResponse { currency: capture.currency, connector: capture.connector, authorized_attempt_id: capture.authorized_attempt_id, - connector_capture_id: capture.connector_capture_id, + connector_capture_id, capture_sequence: capture.capture_sequence, error_message: capture.error_message, error_code: capture.error_code, @@ -1394,7 +1440,8 @@ impl ForeignFrom for api_enums::PaymentMethod { } } -impl ForeignTryFrom<&HeaderMap> for payments::HeaderPayload { +#[cfg(feature = "v1")] +impl ForeignTryFrom<&HeaderMap> for hyperswitch_domain_models::payments::HeaderPayload { type Error = error_stack::Report; fn foreign_try_from(headers: &HeaderMap) -> Result { let payment_confirm_source: Option = @@ -1477,6 +1524,103 @@ impl ForeignTryFrom<&HeaderMap> for payments::HeaderPayload { } } +#[cfg(feature = "v2")] +impl ForeignTryFrom<&HeaderMap> for hyperswitch_domain_models::payments::HeaderPayload { + type Error = error_stack::Report; + fn foreign_try_from(headers: &HeaderMap) -> Result { + use std::str::FromStr; + + use crate::headers::X_CLIENT_SECRET; + + let payment_confirm_source: Option = + get_header_value_by_key(X_PAYMENT_CONFIRM_SOURCE.into(), headers)? + .map(|source| { + source + .to_owned() + .parse_enum("PaymentSource") + .change_context(errors::ApiErrorResponse::InvalidRequestData { + message: "Invalid data received in payment_confirm_source header" + .into(), + }) + .attach_printable( + "Failed while paring PaymentConfirmSource header value to enum", + ) + }) + .transpose()?; + when( + payment_confirm_source.is_some_and(|payment_confirm_source| { + payment_confirm_source.is_for_internal_use_only() + }), + || { + Err(report!(errors::ApiErrorResponse::InvalidRequestData { + message: "Invalid data received in payment_confirm_source header".into(), + })) + }, + )?; + let locale = + get_header_value_by_key(ACCEPT_LANGUAGE.into(), headers)?.map(|val| val.to_string()); + 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()); + + let browser_name_str = + get_header_value_by_key(BROWSER_NAME.into(), headers)?.map(|val| val.to_string()); + + let browser_name: Option = browser_name_str.map(|browser_name| { + browser_name + .parse_enum("BrowserName") + .unwrap_or(api_enums::BrowserName::Unknown) + }); + + let x_client_platform_str = + get_header_value_by_key(X_CLIENT_PLATFORM.into(), headers)?.map(|val| val.to_string()); + + let x_client_platform: Option = + x_client_platform_str.map(|x_client_platform| { + x_client_platform + .parse_enum("ClientPlatform") + .unwrap_or(api_enums::ClientPlatform::Unknown) + }); + + let x_merchant_domain = + get_header_value_by_key(X_MERCHANT_DOMAIN.into(), headers)?.map(|val| val.to_string()); + + let x_app_id = + get_header_value_by_key(X_APP_ID.into(), headers)?.map(|val| val.to_string()); + + let x_redirect_uri = + get_header_value_by_key(X_REDIRECT_URI.into(), headers)?.map(|val| val.to_string()); + + // TODO: combine publishable key and client secret when we unify the auth + let client_secret = get_header_value_by_key(X_CLIENT_SECRET.into(), headers)? + .map(common_utils::types::ClientSecret::from_str) + .transpose() + .change_context(errors::ApiErrorResponse::InvalidRequestData { + message: "Invalid data received in client_secret header".into(), + })?; + + Ok(Self { + payment_confirm_source, + // client_source, + // client_version, + x_hs_latency: Some(x_hs_latency), + browser_name, + x_client_platform, + x_merchant_domain, + locale, + x_app_id, + x_redirect_uri, + client_secret, + }) + } +} + #[cfg(feature = "v1")] impl ForeignTryFrom<( @@ -1518,7 +1662,9 @@ impl field_name: "customer_details", })?; - let mut billing_address = billing.map(api_types::Address::from); + let mut billing_address = billing + .map(hyperswitch_domain_models::address::Address::from) + .map(api_types::Address::from); // This change is to fix a merchant integration // If billing.email is not passed by the merchant, and if the customer email is present, then use the `customer.email` as the billing email @@ -1547,9 +1693,12 @@ impl Ok(Self { currency: payment_attempt.map(|pa| pa.currency.unwrap_or_default()), - shipping: shipping.map(api_types::Address::from), + shipping: shipping + .map(hyperswitch_domain_models::address::Address::from) + .map(api_types::Address::from), billing: billing_address, - amount: payment_attempt.map(|pa| api_types::Amount::from(pa.amount)), + amount: payment_attempt + .map(|pa| api_types::Amount::from(pa.net_amount.get_order_amount())), email: customer .and_then(|cust| cust.email.as_ref().map(|em| pii::Email::from(em.clone()))) .or(customer_details_from_pi.clone().and_then(|cd| cd.email)), @@ -1650,6 +1799,7 @@ impl ForeignFrom for storage::GatewayStatusMapp step_up_possible: value.step_up_possible, unified_code: value.unified_code, unified_message: value.unified_message, + error_category: value.error_category, } } } @@ -1668,6 +1818,7 @@ impl ForeignFrom for gsm_api_types::GsmResponse { step_up_possible: value.step_up_possible, unified_code: value.unified_code, unified_message: value.unified_message, + error_category: value.error_category, } } } @@ -1876,6 +2027,7 @@ impl ForeignFrom .collect() }), allowed_domains: item.allowed_domains, + branding_visibility: item.branding_visibility, } } } @@ -1893,6 +2045,7 @@ impl ForeignFrom .collect() }), allowed_domains: item.allowed_domains, + branding_visibility: item.branding_visibility, } } } @@ -1908,6 +2061,13 @@ impl ForeignFrom sdk_layout: item.sdk_layout, display_sdk_only: item.display_sdk_only, enabled_saved_payment_method: item.enabled_saved_payment_method, + hide_card_nickname_field: item.hide_card_nickname_field, + show_card_form_by_default: item.show_card_form_by_default, + details_layout: item.details_layout, + background_image: item + .background_image + .map(|background_image| background_image.foreign_into()), + payment_button_text: item.payment_button_text, } } } @@ -1923,7 +2083,40 @@ impl ForeignFrom sdk_layout: item.sdk_layout, display_sdk_only: item.display_sdk_only, enabled_saved_payment_method: item.enabled_saved_payment_method, + hide_card_nickname_field: item.hide_card_nickname_field, + show_card_form_by_default: item.show_card_form_by_default, transaction_details: None, + details_layout: item.details_layout, + background_image: item + .background_image + .map(|background_image| background_image.foreign_into()), + payment_button_text: item.payment_button_text, + } + } +} + +impl ForeignFrom + for api_models::admin::PaymentLinkBackgroundImageConfig +{ + fn foreign_from( + item: diesel_models::business_profile::PaymentLinkBackgroundImageConfig, + ) -> Self { + Self { + url: item.url, + position: item.position, + size: item.size, + } + } +} + +impl ForeignFrom + for diesel_models::business_profile::PaymentLinkBackgroundImageConfig +{ + fn foreign_from(item: api_models::admin::PaymentLinkBackgroundImageConfig) -> Self { + Self { + url: item.url, + position: item.position, + size: item.size, } } } diff --git a/crates/router/src/utils.rs b/crates/router/src/utils.rs index a54c2bc8ad6b..e4046f95a153 100644 --- a/crates/router/src/utils.rs +++ b/crates/router/src/utils.rs @@ -13,8 +13,6 @@ pub mod user_role; pub mod verify_connector; use std::fmt::Debug; -#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] -use api_models::payments::AddressDetailsWithPhone; use api_models::{ enums, payments::{self}, @@ -22,10 +20,10 @@ use api_models::{ }; use common_utils::types::keymanager::KeyManagerState; pub use common_utils::{ - crypto, + crypto::{self, Encryptable}, ext_traits::{ByteSliceExt, BytesExt, Encode, StringExt, ValueExt}, fp_utils::when, - id_type, + id_type, pii, validation::validate_email, }; #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] @@ -38,8 +36,8 @@ pub use hyperswitch_connectors::utils::QrImage; use hyperswitch_domain_models::payments::PaymentIntent; #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] use hyperswitch_domain_models::type_encryption::{crypto_operation, CryptoOperation}; +use masking::{ExposeInterface, SwitchStrategy}; use nanoid::nanoid; -use router_env::metrics::add_attributes; use serde::de::DeserializeOwned; use serde_json::Value; use tracing_futures::Instrument; @@ -60,7 +58,10 @@ use crate::{ logger, routes::{metrics, SessionState}, services, - types::{self, domain, transformers::ForeignFrom}, + types::{ + self, domain, + transformers::{ForeignFrom, ForeignInto}, + }, }; pub mod error_parser { @@ -82,7 +83,11 @@ pub mod error_parser { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str( serde_json::to_string(&serde_json::json!({ - "error": self.err.to_string() + "error": { + "error_type": "invalid_request", + "message": self.err.to_string(), + "code": "IR_06", + } })) .as_deref() .unwrap_or("Invalid Json Error"), @@ -672,11 +677,8 @@ pub fn handle_json_response_deserialization_failure( res: types::Response, connector: &'static str, ) -> CustomResult { - metrics::RESPONSE_DESERIALIZATION_FAILURE.add( - &metrics::CONTEXT, - 1, - &add_attributes([("connector", connector)]), - ); + metrics::RESPONSE_DESERIALIZATION_FAILURE + .add(1, router_env::metric_attributes!(("connector", connector))); let response_data = String::from_utf8(res.response.to_vec()) .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; @@ -720,21 +722,11 @@ pub fn add_connector_http_status_code_metrics(option_status_code: Option) { if let Some(status_code) = option_status_code { let status_code_type = get_http_status_code_type(status_code).ok(); match status_code_type.as_deref() { - Some("1xx") => { - metrics::CONNECTOR_HTTP_STATUS_CODE_1XX_COUNT.add(&metrics::CONTEXT, 1, &[]) - } - Some("2xx") => { - metrics::CONNECTOR_HTTP_STATUS_CODE_2XX_COUNT.add(&metrics::CONTEXT, 1, &[]) - } - Some("3xx") => { - metrics::CONNECTOR_HTTP_STATUS_CODE_3XX_COUNT.add(&metrics::CONTEXT, 1, &[]) - } - Some("4xx") => { - metrics::CONNECTOR_HTTP_STATUS_CODE_4XX_COUNT.add(&metrics::CONTEXT, 1, &[]) - } - Some("5xx") => { - metrics::CONNECTOR_HTTP_STATUS_CODE_5XX_COUNT.add(&metrics::CONTEXT, 1, &[]) - } + Some("1xx") => metrics::CONNECTOR_HTTP_STATUS_CODE_1XX_COUNT.add(1, &[]), + Some("2xx") => metrics::CONNECTOR_HTTP_STATUS_CODE_2XX_COUNT.add(1, &[]), + Some("3xx") => metrics::CONNECTOR_HTTP_STATUS_CODE_3XX_COUNT.add(1, &[]), + Some("4xx") => metrics::CONNECTOR_HTTP_STATUS_CODE_4XX_COUNT.add(1, &[]), + Some("5xx") => metrics::CONNECTOR_HTTP_STATUS_CODE_5XX_COUNT.add(1, &[]), _ => logger::info!("Skip metrics as invalid http status code received from connector"), }; } else { @@ -779,20 +771,32 @@ impl CustomerAddress for api_models::customers::CustomerRequest { let encrypted_data = crypto_operation( &state.into(), type_name!(storage::Address), - CryptoOperation::BatchEncrypt(AddressDetailsWithPhone::to_encryptable( - AddressDetailsWithPhone { - address: Some(address_details.clone()), + CryptoOperation::BatchEncrypt(domain::FromRequestEncryptableAddress::to_encryptable( + domain::FromRequestEncryptableAddress { + line1: address_details.line1.clone(), + line2: address_details.line2.clone(), + line3: address_details.line3.clone(), + state: address_details.state.clone(), + first_name: address_details.first_name.clone(), + last_name: address_details.last_name.clone(), + zip: address_details.zip.clone(), phone_number: self.phone.clone(), - email: self.email.clone(), + email: self + .email + .as_ref() + .map(|a| a.clone().expose().switch_strategy()), }, )), - Identifier::Merchant(merchant_id), + Identifier::Merchant(merchant_id.to_owned()), key, ) .await .and_then(|val| val.try_into_batchoperation())?; - let encryptable_address = AddressDetailsWithPhone::from_encryptable(encrypted_data) - .change_context(common_utils::errors::CryptoError::EncodingFailed)?; + + let encryptable_address = + domain::FromRequestEncryptableAddress::from_encryptable(encrypted_data) + .change_context(common_utils::errors::CryptoError::EncodingFailed)?; + Ok(storage::AddressUpdate::Update { city: address_details.city, country: address_details.country, @@ -806,7 +810,14 @@ impl CustomerAddress for api_models::customers::CustomerRequest { phone_number: encryptable_address.phone_number, country_code: self.phone_country_code.clone(), updated_by: storage_scheme.to_string(), - email: encryptable_address.email, + email: encryptable_address.email.map(|email| { + let encryptable: Encryptable> = + Encryptable::new( + email.clone().into_inner().switch_strategy(), + email.into_encrypted(), + ); + encryptable + }), }) } @@ -822,11 +833,20 @@ impl CustomerAddress for api_models::customers::CustomerRequest { let encrypted_data = crypto_operation( &state.into(), type_name!(storage::Address), - CryptoOperation::BatchEncrypt(AddressDetailsWithPhone::to_encryptable( - AddressDetailsWithPhone { - address: Some(address_details.clone()), + CryptoOperation::BatchEncrypt(domain::FromRequestEncryptableAddress::to_encryptable( + domain::FromRequestEncryptableAddress { + line1: address_details.line1.clone(), + line2: address_details.line2.clone(), + line3: address_details.line3.clone(), + state: address_details.state.clone(), + first_name: address_details.first_name.clone(), + last_name: address_details.last_name.clone(), + zip: address_details.zip.clone(), phone_number: self.phone.clone(), - email: self.email.clone(), + email: self + .email + .as_ref() + .map(|a| a.clone().expose().switch_strategy()), }, )), Identifier::Merchant(merchant_id.to_owned()), @@ -834,8 +854,11 @@ impl CustomerAddress for api_models::customers::CustomerRequest { ) .await .and_then(|val| val.try_into_batchoperation())?; - let encryptable_address = AddressDetailsWithPhone::from_encryptable(encrypted_data) - .change_context(common_utils::errors::CryptoError::EncodingFailed)?; + + let encryptable_address = + domain::FromRequestEncryptableAddress::from_encryptable(encrypted_data) + .change_context(common_utils::errors::CryptoError::EncodingFailed)?; + let address = domain::Address { city: address_details.city, country: address_details.country, @@ -853,7 +876,14 @@ impl CustomerAddress for api_models::customers::CustomerRequest { created_at: common_utils::date_time::now(), modified_at: common_utils::date_time::now(), updated_by: storage_scheme.to_string(), - email: encryptable_address.email, + email: encryptable_address.email.map(|email| { + let encryptable: Encryptable> = + Encryptable::new( + email.clone().into_inner().switch_strategy(), + email.into_encrypted(), + ); + encryptable + }), }; Ok(domain::CustomerAddress { @@ -877,20 +907,31 @@ impl CustomerAddress for api_models::customers::CustomerUpdateRequest { let encrypted_data = crypto_operation( &state.into(), type_name!(storage::Address), - CryptoOperation::BatchEncrypt(AddressDetailsWithPhone::to_encryptable( - AddressDetailsWithPhone { - address: Some(address_details.clone()), + CryptoOperation::BatchEncrypt(domain::FromRequestEncryptableAddress::to_encryptable( + domain::FromRequestEncryptableAddress { + line1: address_details.line1.clone(), + line2: address_details.line2.clone(), + line3: address_details.line3.clone(), + state: address_details.state.clone(), + first_name: address_details.first_name.clone(), + last_name: address_details.last_name.clone(), + zip: address_details.zip.clone(), phone_number: self.phone.clone(), - email: self.email.clone(), + email: self + .email + .as_ref() + .map(|a| a.clone().expose().switch_strategy()), }, )), - Identifier::Merchant(merchant_id), + Identifier::Merchant(merchant_id.to_owned()), key, ) .await .and_then(|val| val.try_into_batchoperation())?; - let encryptable_address = AddressDetailsWithPhone::from_encryptable(encrypted_data) - .change_context(common_utils::errors::CryptoError::EncodingFailed)?; + + let encryptable_address = + domain::FromRequestEncryptableAddress::from_encryptable(encrypted_data) + .change_context(common_utils::errors::CryptoError::EncodingFailed)?; Ok(storage::AddressUpdate::Update { city: address_details.city, country: address_details.country, @@ -904,7 +945,14 @@ impl CustomerAddress for api_models::customers::CustomerUpdateRequest { phone_number: encryptable_address.phone_number, country_code: self.phone_country_code.clone(), updated_by: storage_scheme.to_string(), - email: encryptable_address.email, + email: encryptable_address.email.map(|email| { + let encryptable: Encryptable> = + Encryptable::new( + email.clone().into_inner().switch_strategy(), + email.into_encrypted(), + ); + encryptable + }), }) } @@ -920,11 +968,20 @@ impl CustomerAddress for api_models::customers::CustomerUpdateRequest { let encrypted_data = crypto_operation( &state.into(), type_name!(storage::Address), - CryptoOperation::BatchEncrypt(AddressDetailsWithPhone::to_encryptable( - AddressDetailsWithPhone { - address: Some(address_details.clone()), + CryptoOperation::BatchEncrypt(domain::FromRequestEncryptableAddress::to_encryptable( + domain::FromRequestEncryptableAddress { + line1: address_details.line1.clone(), + line2: address_details.line2.clone(), + line3: address_details.line3.clone(), + state: address_details.state.clone(), + first_name: address_details.first_name.clone(), + last_name: address_details.last_name.clone(), + zip: address_details.zip.clone(), phone_number: self.phone.clone(), - email: self.email.clone(), + email: self + .email + .as_ref() + .map(|a| a.clone().expose().switch_strategy()), }, )), Identifier::Merchant(merchant_id.to_owned()), @@ -932,8 +989,10 @@ impl CustomerAddress for api_models::customers::CustomerUpdateRequest { ) .await .and_then(|val| val.try_into_batchoperation())?; - let encryptable_address = AddressDetailsWithPhone::from_encryptable(encrypted_data) - .change_context(common_utils::errors::CryptoError::EncodingFailed)?; + + let encryptable_address = + domain::FromRequestEncryptableAddress::from_encryptable(encrypted_data) + .change_context(common_utils::errors::CryptoError::EncodingFailed)?; let address = domain::Address { city: address_details.city, country: address_details.country, @@ -951,7 +1010,14 @@ impl CustomerAddress for api_models::customers::CustomerUpdateRequest { created_at: common_utils::date_time::now(), modified_at: common_utils::date_time::now(), updated_by: storage_scheme.to_string(), - email: encryptable_address.email, + email: encryptable_address.email.map(|email| { + let encryptable: Encryptable> = + Encryptable::new( + email.clone().into_inner().switch_strategy(), + email.into_encrypted(), + ); + encryptable + }), }; Ok(domain::CustomerAddress { @@ -969,26 +1035,24 @@ pub fn add_apple_pay_flow_metrics( if let Some(flow) = apple_pay_flow { match flow { domain::ApplePayFlow::Simplified(_) => metrics::APPLE_PAY_SIMPLIFIED_FLOW.add( - &metrics::CONTEXT, 1, - &add_attributes([ + router_env::metric_attributes!( ( "connector", connector.to_owned().unwrap_or("null".to_string()), ), - ("merchant_id", merchant_id.get_string_repr().to_owned()), - ]), + ("merchant_id", merchant_id.clone()), + ), ), domain::ApplePayFlow::Manual => metrics::APPLE_PAY_MANUAL_FLOW.add( - &metrics::CONTEXT, 1, - &add_attributes([ + router_env::metric_attributes!( ( "connector", connector.to_owned().unwrap_or("null".to_string()), ), - ("merchant_id", merchant_id.get_string_repr().to_owned()), - ]), + ("merchant_id", merchant_id.clone()), + ), ), } } @@ -1005,28 +1069,26 @@ pub fn add_apple_pay_payment_status_metrics( match flow { domain::ApplePayFlow::Simplified(_) => { metrics::APPLE_PAY_SIMPLIFIED_FLOW_SUCCESSFUL_PAYMENT.add( - &metrics::CONTEXT, 1, - &add_attributes([ + router_env::metric_attributes!( ( "connector", connector.to_owned().unwrap_or("null".to_string()), ), - ("merchant_id", merchant_id.get_string_repr().to_owned()), - ]), + ("merchant_id", merchant_id.clone()), + ), ) } domain::ApplePayFlow::Manual => metrics::APPLE_PAY_MANUAL_FLOW_SUCCESSFUL_PAYMENT .add( - &metrics::CONTEXT, 1, - &add_attributes([ + router_env::metric_attributes!( ( "connector", connector.to_owned().unwrap_or("null".to_string()), ), - ("merchant_id", merchant_id.get_string_repr().to_owned()), - ]), + ("merchant_id", merchant_id.clone()), + ), ), } } @@ -1035,27 +1097,25 @@ pub fn add_apple_pay_payment_status_metrics( match flow { domain::ApplePayFlow::Simplified(_) => { metrics::APPLE_PAY_SIMPLIFIED_FLOW_FAILED_PAYMENT.add( - &metrics::CONTEXT, 1, - &add_attributes([ + router_env::metric_attributes!( ( "connector", connector.to_owned().unwrap_or("null".to_string()), ), - ("merchant_id", merchant_id.get_string_repr().to_owned()), - ]), + ("merchant_id", merchant_id.clone()), + ), ) } domain::ApplePayFlow::Manual => metrics::APPLE_PAY_MANUAL_FLOW_FAILED_PAYMENT.add( - &metrics::CONTEXT, 1, - &add_attributes([ + router_env::metric_attributes!( ( "connector", connector.to_owned().unwrap_or("null".to_string()), ), - ("merchant_id", merchant_id.get_string_repr().to_owned()), - ]), + ("merchant_id", merchant_id.clone()), + ), ), } } @@ -1166,9 +1226,9 @@ where diesel_models::enums::EventClass::Payments, payment_id.get_string_repr().to_owned(), diesel_models::enums::EventObjectType::PaymentDetails, - webhooks::OutgoingWebhookContent::PaymentDetails( + webhooks::OutgoingWebhookContent::PaymentDetails(Box::new( payments_response_json, - ), + )), primary_object_created_at, )) .await @@ -1197,3 +1257,70 @@ pub async fn flatten_join_error(handle: Handle) -> RouterResult { .attach_printable("Join Error"), } } + +#[cfg(feature = "v1")] +pub async fn trigger_refund_outgoing_webhook( + state: &SessionState, + merchant_account: &domain::MerchantAccount, + refund: &diesel_models::Refund, + profile_id: id_type::ProfileId, + key_store: &domain::MerchantKeyStore, +) -> RouterResult<()> { + let refund_status = refund.refund_status; + if matches!( + refund_status, + enums::RefundStatus::Success + | enums::RefundStatus::Failure + | enums::RefundStatus::TransactionFailure + ) { + let event_type = ForeignFrom::foreign_from(refund_status); + let refund_response: api_models::refunds::RefundResponse = refund.clone().foreign_into(); + let key_manager_state = &(state).into(); + let refund_id = refund_response.refund_id.clone(); + let business_profile = state + .store + .find_business_profile_by_profile_id(key_manager_state, key_store, &profile_id) + .await + .to_not_found_response(errors::ApiErrorResponse::ProfileNotFound { + id: profile_id.get_string_repr().to_owned(), + })?; + let cloned_state = state.clone(); + let cloned_key_store = key_store.clone(); + let cloned_merchant_account = merchant_account.clone(); + let primary_object_created_at = refund_response.created_at; + if let Some(outgoing_event_type) = event_type { + tokio::spawn( + async move { + Box::pin(webhooks_core::create_event_and_trigger_outgoing_webhook( + cloned_state, + cloned_merchant_account, + business_profile, + &cloned_key_store, + outgoing_event_type, + diesel_models::enums::EventClass::Refunds, + refund_id.to_string(), + diesel_models::enums::EventObjectType::RefundDetails, + webhooks::OutgoingWebhookContent::RefundDetails(Box::new(refund_response)), + primary_object_created_at, + )) + .await + } + .in_current_span(), + ); + } else { + logger::warn!("Outgoing webhook not sent because of missing event type status mapping"); + }; + } + Ok(()) +} + +#[cfg(feature = "v2")] +pub async fn trigger_refund_outgoing_webhook( + state: &SessionState, + merchant_account: &domain::MerchantAccount, + refund: &diesel_models::Refund, + profile_id: id_type::ProfileId, + key_store: &domain::MerchantKeyStore, +) -> RouterResult<()> { + todo!() +} diff --git a/crates/router/src/utils/connector_onboarding/paypal.rs b/crates/router/src/utils/connector_onboarding/paypal.rs index ebd40fb11dd2..33b85587b430 100644 --- a/crates/router/src/utils/connector_onboarding/paypal.rs +++ b/crates/router/src/utils/connector_onboarding/paypal.rs @@ -16,10 +16,8 @@ use crate::{ pub async fn generate_access_token(state: SessionState) -> RouterResult { let connector = enums::Connector::Paypal; - let boxed_connector = types::api::ConnectorData::convert_connector( - &state.conf.connectors, - connector.to_string().as_str(), - )?; + let boxed_connector = + types::api::ConnectorData::convert_connector(connector.to_string().as_str())?; let connector_auth = super::get_connector_auth(connector, state.conf.connector_onboarding.get_inner())?; diff --git a/crates/router/src/utils/currency.rs b/crates/router/src/utils/currency.rs index dcfe0347d6fa..9ab2780da732 100644 --- a/crates/router/src/utils/currency.rs +++ b/crates/router/src/utils/currency.rs @@ -7,6 +7,7 @@ use error_stack::ResultExt; use masking::PeekInterface; use once_cell::sync::Lazy; use redis_interface::DelReply; +use router_env::{instrument, tracing}; use rust_decimal::Decimal; use strum::IntoEnumIterator; use tokio::{sync::RwLock, time::sleep}; @@ -26,7 +27,7 @@ const FALLBACK_FOREX_API_CURRENCY_PREFIX: &str = "USD"; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct FxExchangeRatesCacheEntry { - data: Arc, + pub data: Arc, timestamp: i64, } @@ -150,11 +151,13 @@ impl TryFrom for ExchangeRates { let mut conversion_usable: HashMap = HashMap::new(); for (curr, conversion) in value.conversion { let enum_curr = enums::Currency::from_str(curr.as_str()) - .change_context(ForexCacheError::ConversionError)?; + .change_context(ForexCacheError::ConversionError) + .attach_printable("Unable to Convert currency received")?; conversion_usable.insert(enum_curr, CurrencyFactors::from(conversion)); } let base_curr = enums::Currency::from_str(value.base_currency.as_str()) - .change_context(ForexCacheError::ConversionError)?; + .change_context(ForexCacheError::ConversionError) + .attach_printable("Unable to convert base currency")?; Ok(Self { base_currency: base_curr, conversion: conversion_usable, @@ -170,6 +173,8 @@ impl From for CurrencyFactors { } } } + +#[instrument(skip_all)] pub async fn get_forex_rates( state: &SessionState, call_delay: i64, @@ -235,6 +240,7 @@ async fn successive_fetch_and_save_forex( Ok(rates) => Ok(successive_save_data_to_redis_local(state, rates).await?), Err(error) => stale_redis_data.ok_or({ logger::error!(?error); + release_redis_lock(state).await?; ForexCacheError::ApiUnresponsive.into() }), } @@ -254,9 +260,9 @@ async fn successive_save_data_to_redis_local( ) -> CustomResult { Ok(save_forex_to_redis(state, &forex) .await - .async_and_then(|_rates| async { release_redis_lock(state).await }) + .async_and_then(|_rates| release_redis_lock(state)) .await - .async_and_then(|_val| async { Ok(save_forex_to_local(forex.clone()).await) }) + .async_and_then(|_val| save_forex_to_local(forex.clone())) .await .map_or_else( |error| { @@ -336,11 +342,15 @@ async fn fetch_forex_rates( false, ) .await - .change_context(ForexCacheError::ApiUnresponsive)?; + .change_context(ForexCacheError::ApiUnresponsive) + .attach_printable("Primary forex fetch api unresponsive")?; let forex_response = response .json::() .await - .change_context(ForexCacheError::ParsingError)?; + .change_context(ForexCacheError::ParsingError) + .attach_printable( + "Unable to parse response received from primary api into ForexResponse", + )?; logger::info!("{:?}", forex_response); @@ -392,11 +402,16 @@ pub async fn fallback_fetch_forex_rates( false, ) .await - .change_context(ForexCacheError::ApiUnresponsive)?; + .change_context(ForexCacheError::ApiUnresponsive) + .attach_printable("Fallback forex fetch api unresponsive")?; + let fallback_forex_response = response .json::() .await - .change_context(ForexCacheError::ParsingError)?; + .change_context(ForexCacheError::ParsingError) + .attach_printable( + "Unable to parse response received from falback api into ForexResponse", + )?; logger::info!("{:?}", fallback_forex_response); let mut conversions: HashMap = HashMap::new(); @@ -421,7 +436,13 @@ pub async fn fallback_fetch_forex_rates( conversions.insert(enum_curr, currency_factors); } None => { - logger::error!("Rates for {} not received from API", &enum_curr); + if enum_curr == enums::Currency::USD { + let currency_factors = + CurrencyFactors::new(Decimal::new(1, 0), Decimal::new(1, 0)); + conversions.insert(enum_curr, currency_factors); + } else { + logger::error!("Rates for {} not received from API", &enum_curr); + } } }; } @@ -447,6 +468,7 @@ async fn release_redis_lock( .delete_key(REDIX_FOREX_CACHE_KEY) .await .change_context(ForexCacheError::RedisLockReleaseFailed) + .attach_printable("Unable to release redis lock") } async fn acquire_redis_lock(state: &SessionState) -> CustomResult { @@ -469,6 +491,7 @@ async fn acquire_redis_lock(state: &SessionState) -> CustomResult Ok(output), Err(redis_error) => match redis_error.current_context() { redis_interface::errors::RedisError::NotFound => { - metrics::KV_MISS.add(&metrics::CONTEXT, 1, &[]); + metrics::KV_MISS.add(1, &[]); database_call_closure().await } // Keeping the key empty here since the error would never go here. @@ -74,7 +74,7 @@ where }), (Err(redis_error), _) => match redis_error.current_context() { redis_interface::errors::RedisError::NotFound => { - metrics::KV_MISS.add(&metrics::CONTEXT, 1, &[]); + metrics::KV_MISS.add(1, &[]); database_call().await } // Keeping the key empty here since the error would never go here. diff --git a/crates/router/src/utils/user.rs b/crates/router/src/utils/user.rs index 22edf6768c84..abd59684243c 100644 --- a/crates/router/src/utils/user.rs +++ b/crates/router/src/utils/user.rs @@ -5,10 +5,11 @@ use common_enums::UserAuthType; use common_utils::{ encryption::Encryption, errors::CustomResult, id_type, type_name, types::keymanager::Identifier, }; -use diesel_models::user_role::UserRole; -use error_stack::{report, ResultExt}; +use diesel_models::{organization, organization::OrganizationBridge}; +use error_stack::ResultExt; use masking::{ExposeInterface, Secret}; use redis_interface::RedisConnectionPool; +use router_env::env; use crate::{ consts::user::{REDIS_SSO_PREFIX, REDIS_SSO_TTL}, @@ -28,6 +29,7 @@ pub mod dashboard_metadata; pub mod password; #[cfg(feature = "dummy_connector")] pub mod sample_data; +pub mod theme; pub mod two_factor_auth; impl UserFromToken { @@ -75,56 +77,12 @@ impl UserFromToken { } pub async fn get_role_info_from_db(&self, state: &SessionState) -> UserResult { - RoleInfo::from_role_id_in_merchant_scope( - state, - &self.role_id, - &self.merchant_id, - &self.org_id, - ) - .await - .change_context(UserErrors::InternalServerError) + RoleInfo::from_role_id_and_org_id(state, &self.role_id, &self.org_id) + .await + .change_context(UserErrors::InternalServerError) } } -pub async fn generate_jwt_auth_token_without_profile( - state: &SessionState, - user: &UserFromStorage, - user_role: &UserRole, -) -> UserResult> { - let token = AuthToken::new_token( - user.get_user_id().to_string(), - user_role - .merchant_id - .as_ref() - .ok_or(report!(UserErrors::InternalServerError)) - .attach_printable("merchant_id not found for user_role")? - .clone(), - user_role.role_id.clone(), - &state.conf, - user_role - .org_id - .as_ref() - .ok_or(report!(UserErrors::InternalServerError)) - .attach_printable("org_id not found for user_role")? - .clone(), - None, - ) - .await?; - Ok(Secret::new(token)) -} - -pub async fn generate_jwt_auth_token_with_attributes_without_profile( - state: &SessionState, - user_id: String, - merchant_id: id_type::MerchantId, - org_id: id_type::OrganizationId, - role_id: String, -) -> UserResult> { - let token = - AuthToken::new_token(user_id, merchant_id, role_id, &state.conf, org_id, None).await?; - Ok(Secret::new(token)) -} - pub async fn generate_jwt_auth_token_with_attributes( state: &SessionState, user_id: String, @@ -132,6 +90,7 @@ pub async fn generate_jwt_auth_token_with_attributes( org_id: id_type::OrganizationId, role_id: String, profile_id: id_type::ProfileId, + tenant_id: Option, ) -> UserResult> { let token = AuthToken::new_token( user_id, @@ -139,7 +98,8 @@ pub async fn generate_jwt_auth_token_with_attributes( role_id, &state.conf, org_id, - Some(profile_id), + profile_id, + tenant_id, ) .await?; Ok(Secret::new(token)) @@ -162,7 +122,7 @@ pub async fn get_user_from_db_by_email( ) -> CustomResult { state .global_store - .find_user_by_email(&email.into_inner()) + .find_user_by_email(&email) .await .map(UserFromStorage::from) } @@ -310,9 +270,45 @@ pub fn get_oidc_sso_redirect_url(state: &SessionState, provider: &str) -> String format!("{}/redirect/oidc/{}", state.conf.user.base_url, provider) } -pub fn is_sso_auth_type(auth_type: &UserAuthType) -> bool { +pub fn is_sso_auth_type(auth_type: UserAuthType) -> bool { match auth_type { UserAuthType::OpenIdConnect => true, UserAuthType::Password | UserAuthType::MagicLink => false, } } + +#[cfg(feature = "v1")] +pub fn create_merchant_account_request_for_org( + req: user_api::UserOrgMerchantCreateRequest, + org: organization::Organization, +) -> UserResult { + let merchant_id = if matches!(env::which(), env::Env::Production) { + id_type::MerchantId::try_from(domain::MerchantId::new(req.merchant_name.clone().expose())?)? + } else { + id_type::MerchantId::new_from_unix_timestamp() + }; + + let company_name = domain::UserCompanyName::new(req.merchant_name.expose())?; + Ok(api_models::admin::MerchantAccountCreate { + merchant_id, + metadata: None, + locker_id: None, + return_url: None, + merchant_name: Some(Secret::new(company_name.get_secret())), + webhook_details: None, + publishable_key: None, + organization_id: Some(org.get_organization_id()), + merchant_details: None, + routing_algorithm: None, + parent_merchant_id: None, + sub_merchants_enabled: None, + frm_routing_algorithm: None, + #[cfg(feature = "payouts")] + payout_routing_algorithm: None, + primary_business_details: None, + payment_response_hash_key: None, + enable_payment_response_hash: None, + redirect_to_merchant_with_http_post: None, + pm_collect_link_config: None, + }) +} diff --git a/crates/router/src/utils/user/dashboard_metadata.rs b/crates/router/src/utils/user/dashboard_metadata.rs index 3abdfdd3abe9..39a76844be1e 100644 --- a/crates/router/src/utils/user/dashboard_metadata.rs +++ b/crates/router/src/utils/user/dashboard_metadata.rs @@ -233,7 +233,7 @@ pub fn is_update_required(metadata: &UserResult) -> bool { } } -pub fn is_backfill_required(metadata_key: &DBEnum) -> bool { +pub fn is_backfill_required(metadata_key: DBEnum) -> bool { matches!( metadata_key, DBEnum::StripeConnected | DBEnum::PaypalConnected diff --git a/crates/router/src/utils/user/sample_data.rs b/crates/router/src/utils/user/sample_data.rs index 1f7d75fba83d..bf84dd568a2a 100644 --- a/crates/router/src/utils/user/sample_data.rs +++ b/crates/router/src/utils/user/sample_data.rs @@ -2,8 +2,13 @@ use api_models::{ enums::Connector::{DummyConnector4, DummyConnector7}, user::sample_data::SampleDataRequest, }; -use common_utils::{id_type, types::MinorUnit}; -use diesel_models::{user::sample_data::PaymentAttemptBatchNew, RefundNew}; +use common_utils::{ + id_type, + types::{ConnectorTransactionId, MinorUnit}, +}; +#[cfg(feature = "v1")] +use diesel_models::user::sample_data::PaymentAttemptBatchNew; +use diesel_models::{enums as storage_enums, DisputeNew, RefundNew}; use error_stack::ResultExt; use hyperswitch_domain_models::payments::PaymentIntent; use rand::{prelude::SliceRandom, thread_rng, Rng}; @@ -22,7 +27,14 @@ pub async fn generate_sample_data( req: SampleDataRequest, merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, -) -> SampleDataResult)>> { +) -> SampleDataResult< + Vec<( + PaymentIntent, + PaymentAttemptBatchNew, + Option, + Option, + )>, +> { let sample_data_size: usize = req.record.unwrap_or(100); let key_manager_state = &state.into(); if !(10..=100).contains(&sample_data_size) { @@ -115,13 +127,23 @@ pub async fn generate_sample_data( let mut refunds_count = 0; + // 2 disputes if generated data size is between 50 and 100, 1 dispute if it is less than 50. + let number_of_disputes: usize = if sample_data_size >= 50 { 2 } else { 1 }; + + let mut disputes_count = 0; + let mut random_array: Vec = (1..=sample_data_size).collect(); // Shuffle the array let mut rng = thread_rng(); random_array.shuffle(&mut rng); - let mut res: Vec<(PaymentIntent, PaymentAttemptBatchNew, Option)> = Vec::new(); + let mut res: Vec<( + PaymentIntent, + PaymentAttemptBatchNew, + Option, + Option, + )> = Vec::new(); let start_time = req .start_time .unwrap_or(common_utils::date_time::now() - time::Duration::days(7)) @@ -241,7 +263,7 @@ pub async fn generate_sample_data( fingerprint_id: None, session_expiry: Some(session_expiry), request_external_three_ds_authentication: None, - charges: None, + split_payments: None, frm_metadata: Default::default(), customer_details: None, billing_details: None, @@ -252,11 +274,15 @@ pub async fn generate_sample_data( shipping_cost: None, tax_details: None, skip_external_tax_calculation: None, + psd2_sca_exemption_type: None, + platform_merchant_id: None, }; + let (connector_transaction_id, connector_transaction_data) = + ConnectorTransactionId::form_id_and_data(attempt_id.clone()); let payment_attempt = PaymentAttemptBatchNew { attempt_id: attempt_id.clone(), payment_id: payment_id.clone(), - connector_transaction_id: Some(attempt_id.clone()), + connector_transaction_id: Some(connector_transaction_id), merchant_id: merchant_id.clone(), status: match is_failed_payment { true => common_enums::AttemptStatus::Failure, @@ -333,10 +359,14 @@ pub async fn generate_sample_data( organization_id: org_id.clone(), shipping_cost: None, order_tax_amount: None, + connector_transaction_data, + connector_mandate_detail: None, }; let refund = if refunds_count < number_of_refunds && !is_failed_payment { refunds_count += 1; + let (connector_transaction_id, connector_transaction_data) = + ConnectorTransactionId::form_id_and_data(attempt_id.clone()); Some(RefundNew { refund_id: common_utils::generate_id_with_default_len("test"), internal_reference_id: common_utils::generate_id_with_default_len("test"), @@ -344,7 +374,7 @@ pub async fn generate_sample_data( payment_id: payment_id.clone(), attempt_id: attempt_id.clone(), merchant_id: merchant_id.clone(), - connector_transaction_id: attempt_id.clone(), + connector_transaction_id, connector_refund_id: None, description: Some("This is a sample refund".to_string()), created_at, @@ -368,13 +398,53 @@ pub async fn generate_sample_data( updated_by: merchant_from_db.storage_scheme.to_string(), merchant_connector_id: payment_attempt.merchant_connector_id.clone(), charges: None, + split_refunds: None, organization_id: org_id.clone(), + connector_refund_data: None, + connector_transaction_data, }) } else { None }; - res.push((payment_intent, payment_attempt, refund)); + let dispute = + if disputes_count < number_of_disputes && !is_failed_payment && refund.is_none() { + disputes_count += 1; + Some(DisputeNew { + dispute_id: common_utils::generate_id_with_default_len("test"), + amount: (amount * 100).to_string(), + currency: payment_intent + .currency + .unwrap_or(common_enums::Currency::USD) + .to_string(), + dispute_stage: storage_enums::DisputeStage::Dispute, + dispute_status: storage_enums::DisputeStatus::DisputeOpened, + payment_id: payment_id.clone(), + attempt_id: attempt_id.clone(), + merchant_id: merchant_id.clone(), + connector_status: "Sample connector status".into(), + connector_dispute_id: common_utils::generate_id_with_default_len("test"), + connector_reason: Some("Sample Dispute".into()), + connector_reason_code: Some("123".into()), + challenge_required_by: None, + connector_created_at: None, + connector_updated_at: None, + connector: payment_attempt + .connector + .clone() + .unwrap_or(DummyConnector4.to_string()), + evidence: None, + profile_id: payment_intent.profile_id.clone(), + merchant_connector_id: payment_attempt.merchant_connector_id.clone(), + dispute_amount: amount * 100, + organization_id: org_id.clone(), + dispute_currency: Some(payment_intent.currency.unwrap_or_default()), + }) + } else { + None + }; + + res.push((payment_intent, payment_attempt, refund, dispute)); } Ok(res) } diff --git a/crates/router/src/utils/user/theme.rs b/crates/router/src/utils/user/theme.rs new file mode 100644 index 000000000000..9cc1c43462c3 --- /dev/null +++ b/crates/router/src/utils/user/theme.rs @@ -0,0 +1,226 @@ +use std::path::PathBuf; + +use common_enums::EntityType; +use common_utils::{ext_traits::AsyncExt, id_type, types::theme::ThemeLineage}; +use diesel_models::user::theme::Theme; +use error_stack::ResultExt; +use hyperswitch_domain_models::merchant_key_store::MerchantKeyStore; + +use crate::{ + core::errors::{StorageErrorExt, UserErrors, UserResult}, + routes::SessionState, + services::authentication::UserFromToken, +}; + +fn get_theme_dir_key(theme_id: &str) -> PathBuf { + ["themes", theme_id].iter().collect() +} + +pub fn get_specific_file_key(theme_id: &str, file_name: &str) -> PathBuf { + let mut path = get_theme_dir_key(theme_id); + path.push(file_name); + path +} + +pub fn get_theme_file_key(theme_id: &str) -> PathBuf { + get_specific_file_key(theme_id, "theme.json") +} + +fn path_buf_to_str(path: &PathBuf) -> UserResult<&str> { + path.to_str() + .ok_or(UserErrors::InternalServerError) + .attach_printable(format!("Failed to convert path {:#?} to string", path)) +} + +pub async fn retrieve_file_from_theme_bucket( + state: &SessionState, + path: &PathBuf, +) -> UserResult> { + state + .theme_storage_client + .retrieve_file(path_buf_to_str(path)?) + .await + .change_context(UserErrors::ErrorRetrievingFile) +} + +pub async fn upload_file_to_theme_bucket( + state: &SessionState, + path: &PathBuf, + data: Vec, +) -> UserResult<()> { + state + .theme_storage_client + .upload_file(path_buf_to_str(path)?, data) + .await + .change_context(UserErrors::ErrorUploadingFile) +} + +pub async fn validate_lineage(state: &SessionState, lineage: &ThemeLineage) -> UserResult<()> { + match lineage { + ThemeLineage::Tenant { tenant_id } => { + validate_tenant(state, tenant_id)?; + Ok(()) + } + ThemeLineage::Organization { tenant_id, org_id } => { + validate_tenant(state, tenant_id)?; + validate_org(state, org_id).await?; + Ok(()) + } + ThemeLineage::Merchant { + tenant_id, + org_id, + merchant_id, + } => { + validate_tenant(state, tenant_id)?; + validate_org(state, org_id).await?; + validate_merchant(state, org_id, merchant_id).await?; + Ok(()) + } + ThemeLineage::Profile { + tenant_id, + org_id, + merchant_id, + profile_id, + } => { + validate_tenant(state, tenant_id)?; + validate_org(state, org_id).await?; + let key_store = validate_merchant_and_get_key_store(state, org_id, merchant_id).await?; + validate_profile(state, profile_id, merchant_id, &key_store).await?; + Ok(()) + } + } +} + +fn validate_tenant(state: &SessionState, tenant_id: &id_type::TenantId) -> UserResult<()> { + if &state.tenant.tenant_id != tenant_id { + return Err(UserErrors::InvalidThemeLineage("tenant_id".to_string()).into()); + } + Ok(()) +} + +async fn validate_org(state: &SessionState, org_id: &id_type::OrganizationId) -> UserResult<()> { + state + .store + .find_organization_by_org_id(org_id) + .await + .to_not_found_response(UserErrors::InvalidThemeLineage("org_id".to_string())) + .map(|_| ()) +} + +async fn validate_merchant_and_get_key_store( + state: &SessionState, + org_id: &id_type::OrganizationId, + merchant_id: &id_type::MerchantId, +) -> UserResult { + let key_store = state + .store + .get_merchant_key_store_by_merchant_id( + &state.into(), + merchant_id, + &state.store.get_master_key().to_vec().into(), + ) + .await + .to_not_found_response(UserErrors::InvalidThemeLineage("merchant_id".to_string()))?; + + let merchant_account = state + .store + .find_merchant_account_by_merchant_id(&state.into(), merchant_id, &key_store) + .await + .to_not_found_response(UserErrors::InvalidThemeLineage("merchant_id".to_string()))?; + + if &merchant_account.organization_id != org_id { + return Err(UserErrors::InvalidThemeLineage("merchant_id".to_string()).into()); + } + + Ok(key_store) +} + +async fn validate_merchant( + state: &SessionState, + org_id: &id_type::OrganizationId, + merchant_id: &id_type::MerchantId, +) -> UserResult<()> { + validate_merchant_and_get_key_store(state, org_id, merchant_id) + .await + .map(|_| ()) +} + +async fn validate_profile( + state: &SessionState, + profile_id: &id_type::ProfileId, + merchant_id: &id_type::MerchantId, + key_store: &MerchantKeyStore, +) -> UserResult<()> { + state + .store + .find_business_profile_by_merchant_id_profile_id( + &state.into(), + key_store, + merchant_id, + profile_id, + ) + .await + .to_not_found_response(UserErrors::InvalidThemeLineage("profile_id".to_string())) + .map(|_| ()) +} + +pub async fn get_most_specific_theme_using_token_and_min_entity( + state: &SessionState, + user_from_token: &UserFromToken, + min_entity: EntityType, +) -> UserResult> { + get_most_specific_theme_using_lineage( + state, + ThemeLineage::new( + min_entity, + user_from_token + .tenant_id + .clone() + .unwrap_or(state.tenant.tenant_id.clone()), + user_from_token.org_id.clone(), + user_from_token.merchant_id.clone(), + user_from_token.profile_id.clone(), + ), + ) + .await +} + +pub async fn get_most_specific_theme_using_lineage( + state: &SessionState, + lineage: ThemeLineage, +) -> UserResult> { + match state + .store + .find_most_specific_theme_in_lineage(lineage) + .await + { + Ok(theme) => Ok(Some(theme)), + Err(e) => { + if e.current_context().is_db_not_found() { + Ok(None) + } else { + Err(e.change_context(UserErrors::InternalServerError)) + } + } + } +} + +pub async fn get_theme_using_optional_theme_id( + state: &SessionState, + theme_id: Option, +) -> UserResult> { + match theme_id + .async_map(|theme_id| state.store.find_theme_by_theme_id(theme_id)) + .await + .transpose() + { + Ok(theme) => Ok(theme), + Err(e) => { + if e.current_context().is_db_not_found() { + Ok(None) + } else { + Err(e.change_context(UserErrors::InternalServerError)) + } + } + } +} diff --git a/crates/router/src/utils/user/two_factor_auth.rs b/crates/router/src/utils/user/two_factor_auth.rs index bebe58ebd866..73d692a00e06 100644 --- a/crates/router/src/utils/user/two_factor_auth.rs +++ b/crates/router/src/utils/user/two_factor_auth.rs @@ -139,3 +139,90 @@ pub async fn delete_recovery_code_from_redis( .change_context(UserErrors::InternalServerError) .map(|_| ()) } + +fn get_totp_attempts_key(user_id: &str) -> String { + format!("{}{}", consts::user::REDIS_TOTP_ATTEMPTS_PREFIX, user_id) +} +fn get_recovery_code_attempts_key(user_id: &str) -> String { + format!( + "{}{}", + consts::user::REDIS_RECOVERY_CODE_ATTEMPTS_PREFIX, + user_id + ) +} + +pub async fn insert_totp_attempts_in_redis( + state: &SessionState, + user_id: &str, + user_totp_attempts: u8, +) -> UserResult<()> { + let redis_conn = super::get_redis_connection(state)?; + redis_conn + .set_key_with_expiry( + &get_totp_attempts_key(user_id), + user_totp_attempts, + consts::user::REDIS_TOTP_ATTEMPTS_TTL_IN_SECS, + ) + .await + .change_context(UserErrors::InternalServerError) +} +pub async fn get_totp_attempts_from_redis(state: &SessionState, user_id: &str) -> UserResult { + let redis_conn = super::get_redis_connection(state)?; + redis_conn + .get_key::>(&get_totp_attempts_key(user_id)) + .await + .change_context(UserErrors::InternalServerError) + .map(|v| v.unwrap_or(0)) +} + +pub async fn insert_recovery_code_attempts_in_redis( + state: &SessionState, + user_id: &str, + user_recovery_code_attempts: u8, +) -> UserResult<()> { + let redis_conn = super::get_redis_connection(state)?; + redis_conn + .set_key_with_expiry( + &get_recovery_code_attempts_key(user_id), + user_recovery_code_attempts, + consts::user::REDIS_RECOVERY_CODE_ATTEMPTS_TTL_IN_SECS, + ) + .await + .change_context(UserErrors::InternalServerError) +} + +pub async fn get_recovery_code_attempts_from_redis( + state: &SessionState, + user_id: &str, +) -> UserResult { + let redis_conn = super::get_redis_connection(state)?; + redis_conn + .get_key::>(&get_recovery_code_attempts_key(user_id)) + .await + .change_context(UserErrors::InternalServerError) + .map(|v| v.unwrap_or(0)) +} + +pub async fn delete_totp_attempts_from_redis( + state: &SessionState, + user_id: &str, +) -> UserResult<()> { + let redis_conn = super::get_redis_connection(state)?; + redis_conn + .delete_key(&get_totp_attempts_key(user_id)) + .await + .change_context(UserErrors::InternalServerError) + .map(|_| ()) +} + +pub async fn delete_recovery_code_attempts_from_redis( + state: &SessionState, + user_id: &str, +) -> UserResult<()> { + let redis_conn = super::get_redis_connection(state)?; + redis_conn + .delete_key(&get_recovery_code_attempts_key(user_id)) + .await + .change_context(UserErrors::InternalServerError) + .map(|_| ()) +} diff --git a/crates/router/src/utils/user_role.rs b/crates/router/src/utils/user_role.rs index b6db83407756..ac8ee11fc6a2 100644 --- a/crates/router/src/utils/user_role.rs +++ b/crates/router/src/utils/user_role.rs @@ -1,6 +1,5 @@ use std::{cmp, collections::HashSet}; -use api_models::user_role as user_role_api; use common_enums::{EntityType, PermissionGroup}; use common_utils::id_type; use diesel_models::{ @@ -14,58 +13,22 @@ use storage_impl::errors::StorageError; use crate::{ consts, core::errors::{UserErrors, UserResult}, - db::user_role::{ListUserRolesByOrgIdPayload, ListUserRolesByUserIdPayload}, + db::{ + errors::StorageErrorExt, + user_role::{ListUserRolesByOrgIdPayload, ListUserRolesByUserIdPayload}, + }, routes::SessionState, - services::authorization::{self as authz, permissions::Permission, roles}, + services::authorization::{self as authz, roles}, types::domain, }; -impl From for user_role_api::Permission { - fn from(value: Permission) -> Self { - match value { - Permission::PaymentRead => Self::PaymentRead, - Permission::PaymentWrite => Self::PaymentWrite, - Permission::RefundRead => Self::RefundRead, - Permission::RefundWrite => Self::RefundWrite, - Permission::ApiKeyRead => Self::ApiKeyRead, - Permission::ApiKeyWrite => Self::ApiKeyWrite, - Permission::MerchantAccountRead => Self::MerchantAccountRead, - Permission::MerchantAccountWrite => Self::MerchantAccountWrite, - Permission::MerchantConnectorAccountRead => Self::MerchantConnectorAccountRead, - Permission::MerchantConnectorAccountWrite => Self::MerchantConnectorAccountWrite, - Permission::RoutingRead => Self::RoutingRead, - Permission::RoutingWrite => Self::RoutingWrite, - Permission::DisputeRead => Self::DisputeRead, - Permission::DisputeWrite => Self::DisputeWrite, - Permission::MandateRead => Self::MandateRead, - Permission::MandateWrite => Self::MandateWrite, - Permission::CustomerRead => Self::CustomerRead, - Permission::CustomerWrite => Self::CustomerWrite, - Permission::Analytics => Self::Analytics, - Permission::ThreeDsDecisionManagerWrite => Self::ThreeDsDecisionManagerWrite, - Permission::ThreeDsDecisionManagerRead => Self::ThreeDsDecisionManagerRead, - Permission::SurchargeDecisionManagerWrite => Self::SurchargeDecisionManagerWrite, - Permission::SurchargeDecisionManagerRead => Self::SurchargeDecisionManagerRead, - Permission::UsersRead => Self::UsersRead, - Permission::UsersWrite => Self::UsersWrite, - Permission::MerchantAccountCreate => Self::MerchantAccountCreate, - Permission::WebhookEventRead => Self::WebhookEventRead, - Permission::WebhookEventWrite => Self::WebhookEventWrite, - Permission::PayoutRead => Self::PayoutRead, - Permission::PayoutWrite => Self::PayoutWrite, - Permission::GenerateReport => Self::GenerateReport, - Permission::ReconAdmin => Self::ReconAdmin, - } - } -} - pub fn validate_role_groups(groups: &[PermissionGroup]) -> UserResult<()> { if groups.is_empty() { return Err(report!(UserErrors::InvalidRoleOperation)) .attach_printable("Role groups cannot be empty"); } - let unique_groups: HashSet<_> = groups.iter().cloned().collect(); + let unique_groups: HashSet<_> = groups.iter().copied().collect(); if unique_groups.contains(&PermissionGroup::OrganizationManage) { return Err(report!(UserErrors::InvalidRoleOperation)) @@ -94,7 +57,7 @@ pub async fn validate_role_name( // TODO: Create and use find_by_role_name to make this efficient let is_present_in_custom_roles = state - .store + .global_store .list_all_roles(merchant_id, org_id) .await .change_context(UserErrors::InternalServerError)? @@ -108,55 +71,43 @@ pub async fn validate_role_name( Ok(()) } -pub async fn set_role_permissions_in_cache_by_user_role( +pub async fn set_role_info_in_cache_by_user_role( state: &SessionState, user_role: &UserRole, ) -> bool { - let Some(ref merchant_id) = user_role.merchant_id else { - return false; - }; - let Some(ref org_id) = user_role.org_id else { return false; }; - set_role_permissions_in_cache_if_required( - state, - user_role.role_id.as_str(), - merchant_id, - org_id, - ) - .await - .map_err(|e| logger::error!("Error setting permissions in cache {:?}", e)) - .is_ok() + set_role_info_in_cache_if_required(state, user_role.role_id.as_str(), org_id) + .await + .map_err(|e| logger::error!("Error setting permissions in cache {:?}", e)) + .is_ok() } -pub async fn set_role_permissions_in_cache_by_role_id_merchant_id_org_id( +pub async fn set_role_info_in_cache_by_role_id_org_id( state: &SessionState, role_id: &str, - merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, ) -> bool { - set_role_permissions_in_cache_if_required(state, role_id, merchant_id, org_id) + set_role_info_in_cache_if_required(state, role_id, org_id) .await .map_err(|e| logger::error!("Error setting permissions in cache {:?}", e)) .is_ok() } -pub async fn set_role_permissions_in_cache_if_required( +pub async fn set_role_info_in_cache_if_required( state: &SessionState, role_id: &str, - merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, ) -> UserResult<()> { if roles::predefined_roles::PREDEFINED_ROLES.contains_key(role_id) { return Ok(()); } - let role_info = - roles::RoleInfo::from_role_id_in_merchant_scope(state, role_id, merchant_id, org_id) - .await - .change_context(UserErrors::InternalServerError) - .attach_printable("Error getting role_info from role_id")?; + let role_info = roles::RoleInfo::from_role_id_and_org_id(state, role_id, org_id) + .await + .change_context(UserErrors::InternalServerError) + .attach_printable("Error getting role_info from role_id")?; authz::set_role_info_in_cache( state, @@ -173,8 +124,9 @@ pub async fn set_role_permissions_in_cache_if_required( pub async fn update_v1_and_v2_user_roles_in_db( state: &SessionState, user_id: &str, + tenant_id: &id_type::TenantId, org_id: &id_type::OrganizationId, - merchant_id: &id_type::MerchantId, + merchant_id: Option<&id_type::MerchantId>, profile_id: Option<&id_type::ProfileId>, update: UserRoleUpdate, ) -> ( @@ -182,9 +134,10 @@ pub async fn update_v1_and_v2_user_roles_in_db( Result>, ) { let updated_v1_role = state - .store + .global_store .update_user_role_by_user_id_and_lineage( user_id, + tenant_id, org_id, merchant_id, profile_id, @@ -198,9 +151,10 @@ pub async fn update_v1_and_v2_user_roles_in_db( }); let updated_v2_role = state - .store + .global_store .update_user_role_by_user_id_and_lineage( user_id, + tenant_id, org_id, merchant_id, profile_id, @@ -216,30 +170,54 @@ pub async fn update_v1_and_v2_user_roles_in_db( (updated_v1_role, updated_v2_role) } +pub async fn get_single_org_id( + state: &SessionState, + user_role: &UserRole, +) -> UserResult { + let (_, entity_type) = user_role + .get_entity_id_and_type() + .ok_or(UserErrors::InternalServerError)?; + match entity_type { + EntityType::Tenant => Ok(state + .store + .list_merchant_and_org_ids(&state.into(), 1, None) + .await + .change_context(UserErrors::InternalServerError) + .attach_printable("Failed to get merchants list for org")? + .pop() + .ok_or(UserErrors::InternalServerError) + .attach_printable("No merchants to get merchant or org id")? + .1), + EntityType::Organization | EntityType::Merchant | EntityType::Profile => user_role + .org_id + .clone() + .ok_or(UserErrors::InternalServerError) + .attach_printable("Org_id not found"), + } +} + pub async fn get_single_merchant_id( state: &SessionState, user_role: &UserRole, + org_id: &id_type::OrganizationId, ) -> UserResult { - match user_role.entity_type { - Some(EntityType::Organization) => Ok(state + let (_, entity_type) = user_role + .get_entity_id_and_type() + .ok_or(UserErrors::InternalServerError)?; + match entity_type { + EntityType::Tenant | EntityType::Organization => Ok(state .store - .list_merchant_accounts_by_organization_id( - &state.into(), - user_role - .org_id - .as_ref() - .ok_or(UserErrors::InternalServerError) - .attach_printable("org_id not found")?, - ) + .list_merchant_accounts_by_organization_id(&state.into(), org_id) .await - .change_context(UserErrors::InternalServerError) - .attach_printable("Failed to get merchant list for org")? + .to_not_found_response(UserErrors::InvalidRoleOperationWithMessage( + "Invalid Org Id".to_string(), + ))? .first() .ok_or(UserErrors::InternalServerError) .attach_printable("No merchants found for org_id")? .get_id() .clone()), - Some(EntityType::Merchant) | Some(EntityType::Profile) | None => user_role + EntityType::Merchant | EntityType::Profile => user_role .merchant_id .clone() .ok_or(UserErrors::InternalServerError) @@ -247,29 +225,119 @@ pub async fn get_single_merchant_id( } } +pub async fn get_single_profile_id( + state: &SessionState, + user_role: &UserRole, + merchant_id: &id_type::MerchantId, +) -> UserResult { + let (_, entity_type) = user_role + .get_entity_id_and_type() + .ok_or(UserErrors::InternalServerError)?; + match entity_type { + EntityType::Tenant | EntityType::Organization | EntityType::Merchant => { + let key_store = state + .store + .get_merchant_key_store_by_merchant_id( + &state.into(), + merchant_id, + &state.store.get_master_key().to_vec().into(), + ) + .await + .change_context(UserErrors::InternalServerError)?; + + Ok(state + .store + .list_profile_by_merchant_id(&state.into(), &key_store, merchant_id) + .await + .change_context(UserErrors::InternalServerError)? + .pop() + .ok_or(UserErrors::InternalServerError)? + .get_id() + .to_owned()) + } + EntityType::Profile => user_role + .profile_id + .clone() + .ok_or(UserErrors::InternalServerError) + .attach_printable("profile_id not found"), + } +} + pub async fn get_lineage_for_user_id_and_entity_for_accepting_invite( state: &SessionState, user_id: &str, + tenant_id: &id_type::TenantId, entity_id: String, entity_type: EntityType, ) -> UserResult< Option<( id_type::OrganizationId, - id_type::MerchantId, + Option, Option, )>, > { match entity_type { - EntityType::Organization => Err(UserErrors::InvalidRoleOperation.into()), + EntityType::Tenant => Err(UserErrors::InvalidRoleOperationWithMessage( + "Tenant roles are not allowed for this operation".to_string(), + ) + .into()), + EntityType::Organization => { + let Ok(org_id) = + id_type::OrganizationId::try_from(std::borrow::Cow::from(entity_id.clone())) + else { + return Ok(None); + }; + + let user_roles = state + .global_store + .list_user_roles_by_user_id(ListUserRolesByUserIdPayload { + user_id, + tenant_id, + org_id: Some(&org_id), + merchant_id: None, + profile_id: None, + entity_id: None, + version: None, + status: Some(UserStatus::InvitationSent), + limit: None, + }) + .await + .change_context(UserErrors::InternalServerError)? + .into_iter() + .collect::>(); + + if user_roles.len() > 1 { + return Ok(None); + } + + if let Some(user_role) = user_roles.into_iter().next() { + let (_entity_id, entity_type) = user_role + .get_entity_id_and_type() + .ok_or(UserErrors::InternalServerError)?; + + if entity_type != EntityType::Organization { + return Ok(None); + } + + return Ok(Some(( + user_role.org_id.ok_or(UserErrors::InternalServerError)?, + None, + None, + ))); + } + + Ok(None) + } EntityType::Merchant => { let Ok(merchant_id) = id_type::MerchantId::wrap(entity_id) else { return Ok(None); }; let user_roles = state - .store + .global_store .list_user_roles_by_user_id(ListUserRolesByUserIdPayload { user_id, + tenant_id, org_id: None, merchant_id: Some(&merchant_id), profile_id: None, @@ -298,7 +366,7 @@ pub async fn get_lineage_for_user_id_and_entity_for_accepting_invite( return Ok(Some(( user_role.org_id.ok_or(UserErrors::InternalServerError)?, - merchant_id, + Some(merchant_id), None, ))); } @@ -312,9 +380,10 @@ pub async fn get_lineage_for_user_id_and_entity_for_accepting_invite( }; let user_roles = state - .store + .global_store .list_user_roles_by_user_id(ListUserRolesByUserIdPayload { user_id, + tenant_id: &state.tenant.tenant_id, org_id: None, merchant_id: None, profile_id: Some(&profile_id), @@ -343,9 +412,11 @@ pub async fn get_lineage_for_user_id_and_entity_for_accepting_invite( return Ok(Some(( user_role.org_id.ok_or(UserErrors::InternalServerError)?, - user_role - .merchant_id - .ok_or(UserErrors::InternalServerError)?, + Some( + user_role + .merchant_id + .ok_or(UserErrors::InternalServerError)?, + ), Some(profile_id), ))); } @@ -359,37 +430,9 @@ pub async fn get_single_merchant_id_and_profile_id( state: &SessionState, user_role: &UserRole, ) -> UserResult<(id_type::MerchantId, id_type::ProfileId)> { - let merchant_id = get_single_merchant_id(state, user_role).await?; - let (_, entity_type) = user_role - .get_entity_id_and_type() - .ok_or(UserErrors::InternalServerError)?; - let profile_id = match entity_type { - EntityType::Organization | EntityType::Merchant => { - let key_store = state - .store - .get_merchant_key_store_by_merchant_id( - &state.into(), - &merchant_id, - &state.store.get_master_key().to_vec().into(), - ) - .await - .change_context(UserErrors::InternalServerError)?; - - state - .store - .list_profile_by_merchant_id(&state.into(), &key_store, &merchant_id) - .await - .change_context(UserErrors::InternalServerError)? - .pop() - .ok_or(UserErrors::InternalServerError)? - .get_id() - .to_owned() - } - EntityType::Profile => user_role - .profile_id - .clone() - .ok_or(UserErrors::InternalServerError)?, - }; + let org_id = get_single_org_id(state, user_role).await?; + let merchant_id = get_single_merchant_id(state, user_role, &org_id).await?; + let profile_id = get_single_profile_id(state, user_role, &merchant_id).await?; Ok((merchant_id, profile_id)) } @@ -400,7 +443,7 @@ pub async fn fetch_user_roles_by_payload( request_entity_type: Option, ) -> UserResult> { Ok(state - .store + .global_store .list_user_roles_by_org_id(payload) .await .change_context(UserErrors::InternalServerError)? diff --git a/crates/router/src/utils/verify_connector.rs b/crates/router/src/utils/verify_connector.rs index 617cb415ee00..b13ba70e6fbd 100644 --- a/crates/router/src/utils/verify_connector.rs +++ b/crates/router/src/utils/verify_connector.rs @@ -23,6 +23,7 @@ pub fn generate_card_from_details( card_type: None, card_issuing_country: None, bank_code: None, + card_holder_name: None, }) } diff --git a/crates/router/src/workflows/api_key_expiry.rs b/crates/router/src/workflows/api_key_expiry.rs index b26e9862e81e..2ff527a046e5 100644 --- a/crates/router/src/workflows/api_key_expiry.rs +++ b/crates/router/src/workflows/api_key_expiry.rs @@ -1,17 +1,17 @@ -use common_utils::{errors::ValidationError, ext_traits::ValueExt}; +use common_utils::{errors::ValidationError, ext_traits::ValueExt, types::theme::ThemeLineage}; use diesel_models::{ enums as storage_enums, process_tracker::business_status, ApiKeyExpiryTrackingData, }; -use router_env::{logger, metrics::add_attributes}; +use router_env::logger; use scheduler::{workflows::ProcessTrackerWorkflow, SchedulerSessionState}; use crate::{ - errors, + consts, errors, logger::error, routes::{metrics, SessionState}, - services::email::types::ApiKeyExpiryReminder, + services::email::types::{self as email_types, ApiKeyExpiryReminder}, types::{api, domain::UserEmail, storage}, - utils::OptionExt, + utils::{user::theme as theme_utils, OptionExt}, }; pub struct ApiKeyExpiryWorkflow; @@ -48,6 +48,7 @@ impl ProcessTrackerWorkflow for ApiKeyExpiryWorkflow { let email_id = merchant_account .merchant_details + .clone() .parse_value::("MerchantDetails")? .primary_email .ok_or(errors::ProcessTrackerError::EValidationError( @@ -73,6 +74,20 @@ impl ProcessTrackerWorkflow for ApiKeyExpiryWorkflow { ) .ok_or(errors::ProcessTrackerError::EApiErrorResponse)?; + let theme = theme_utils::get_most_specific_theme_using_lineage( + state, + ThemeLineage::Merchant { + tenant_id: state.tenant.tenant_id.clone(), + org_id: merchant_account.get_org_id().clone(), + merchant_id: merchant_account.get_id().clone(), + }, + ) + .await + .map_err(|err| { + logger::error!(?err, "Failed to get theme"); + errors::ProcessTrackerError::EApiErrorResponse + })?; + let email_contents = ApiKeyExpiryReminder { recipient_email: UserEmail::from_pii_email(email_id).map_err(|error| { logger::error!( @@ -81,16 +96,21 @@ impl ProcessTrackerWorkflow for ApiKeyExpiryWorkflow { ); errors::ProcessTrackerError::EApiErrorResponse })?, - subject: "API Key Expiry Notice", + subject: consts::EMAIL_SUBJECT_API_KEY_EXPIRY, expires_in: *expires_in, api_key_name, prefix, + theme_id: theme.as_ref().map(|theme| theme.theme_id.clone()), + theme_config: theme + .map(|theme| theme.email_config()) + .unwrap_or(state.conf.theme.email_config.clone()), }; state .email_client .clone() .compose_and_send_email( + email_types::get_base_url(state), Box::new(email_contents), state.conf.proxy.https_url.as_ref(), ) @@ -134,11 +154,8 @@ impl ProcessTrackerWorkflow for ApiKeyExpiryWorkflow { db.process_tracker_update_process_status_by_ids(task_ids, updated_process_tracker_data) .await?; // Remaining tasks are re-scheduled, so will be resetting the added count - metrics::TASKS_RESET_COUNT.add( - &metrics::CONTEXT, - 1, - &add_attributes([("flow", "ApiKeyExpiry")]), - ); + metrics::TASKS_RESET_COUNT + .add(1, router_env::metric_attributes!(("flow", "ApiKeyExpiry"))); } Ok(()) diff --git a/crates/router/src/workflows/outgoing_webhook_retry.rs b/crates/router/src/workflows/outgoing_webhook_retry.rs index 89fd3200d7de..c7df4aeff0cd 100644 --- a/crates/router/src/workflows/outgoing_webhook_retry.rs +++ b/crates/router/src/workflows/outgoing_webhook_retry.rs @@ -344,7 +344,7 @@ async fn get_outgoing_webhook_content_and_event_type( ) -> Result<(OutgoingWebhookContent, Option), errors::ProcessTrackerError> { use api_models::{ mandates::MandateId, - payments::{HeaderPayload, PaymentIdType, PaymentsResponse, PaymentsRetrieveRequest}, + payments::{PaymentIdType, PaymentsResponse, PaymentsRetrieveRequest}, refunds::{RefundResponse, RefundsRetrieveRequest}, }; @@ -399,7 +399,8 @@ async fn get_outgoing_webhook_content_and_event_type( AuthFlow::Client, CallConnectorAction::Avoid, None, - HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), + None, //Platform merchant account )) .await? { @@ -423,7 +424,7 @@ async fn get_outgoing_webhook_content_and_event_type( logger::debug!(current_resource_status=%payments_response.status); Ok(( - OutgoingWebhookContent::PaymentDetails(payments_response), + OutgoingWebhookContent::PaymentDetails(Box::new(payments_response)), event_type, )) } @@ -449,7 +450,7 @@ async fn get_outgoing_webhook_content_and_event_type( let refund_response = RefundResponse::foreign_from(refund); Ok(( - OutgoingWebhookContent::RefundDetails(refund_response), + OutgoingWebhookContent::RefundDetails(Box::new(refund_response)), event_type, )) } @@ -548,7 +549,7 @@ async fn get_outgoing_webhook_content_and_event_type( logger::debug!(current_resource_status=%payout_data.payout_attempt.status); Ok(( - OutgoingWebhookContent::PayoutDetails(payout_create_response), + OutgoingWebhookContent::PayoutDetails(Box::new(payout_create_response)), event_type, )) } diff --git a/crates/router/src/workflows/payment_sync.rs b/crates/router/src/workflows/payment_sync.rs index f34d707b6c83..fb83922935e3 100644 --- a/crates/router/src/workflows/payment_sync.rs +++ b/crates/router/src/workflows/payment_sync.rs @@ -90,7 +90,8 @@ impl ProcessTrackerWorkflow for PaymentsSyncWorkflow { payment_flows::CallConnectorAction::Trigger, services::AuthFlow::Client, None, - api::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), + None, //Platform merchant account )) .await?; @@ -243,7 +244,6 @@ impl ProcessTrackerWorkflow for PaymentsSyncWorkflow { /// `start_after`: The first psync should happen after 60 seconds /// /// `frequency` and `count`: The next 5 retries should have an interval of 300 seconds between them -/// pub async fn get_sync_process_schedule_time( db: &dyn StorageInterface, connector: &str, diff --git a/crates/router/tests/cache.rs b/crates/router/tests/cache.rs index 8c3f34cd1e1b..55b92b4aace5 100644 --- a/crates/router/tests/cache.rs +++ b/crates/router/tests/cache.rs @@ -18,7 +18,10 @@ async fn invalidate_existing_cache_success() { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let cache_key = "cacheKey".to_string(); let cache_key_value = "val".to_string(); diff --git a/crates/router/tests/connectors/aci.rs b/crates/router/tests/connectors/aci.rs index 5d48a0188ff5..18e9c43ed6ac 100644 --- a/crates/router/tests/connectors/aci.rs +++ b/crates/router/tests/connectors/aci.rs @@ -2,8 +2,8 @@ use std::{borrow::Cow, marker::PhantomData, str::FromStr, sync::Arc}; -use api_models::payments::{Address, AddressDetails, PhoneDetails}; use common_utils::id_type; +use hyperswitch_domain_models::address::{Address, AddressDetails, PhoneDetails}; use masking::Secret; use router::{ configs::settings::Settings, @@ -35,7 +35,6 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData { payment_method: enums::PaymentMethod::Card, connector_auth_type: utils::to_connector_auth_type(auth.into()), description: Some("This is a test".to_string()), - return_url: None, payment_method_status: None, request: types::PaymentsAuthorizeData { amount: 1000, @@ -51,6 +50,7 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData { card_issuing_country: None, bank_code: None, nick_name: Some(Secret::new("nick_name".into())), + card_holder_name: Some(Secret::new("card holder name".into())), }), confirm: true, statement_descriptor_suffix: None, @@ -128,6 +128,9 @@ fn construct_payment_router_data() -> types::PaymentsAuthorizeRouterData { integrity_check: Ok(()), additional_merchant_data: None, header_payload: None, + connector_mandate_request_reference_id: None, + authentication_id: None, + psd2_sca_exemption_type: None, } } @@ -151,7 +154,6 @@ fn construct_refund_router_data() -> types::RefundsRouterData { auth_type: enums::AuthenticationType::NoThreeDs, connector_auth_type: utils::to_connector_auth_type(auth.into()), description: Some("This is a test".to_string()), - return_url: None, request: types::RefundsData { payment_amount: 1000, currency: enums::Currency::USD, @@ -197,11 +199,13 @@ fn construct_refund_router_data() -> types::RefundsRouterData { integrity_check: Ok(()), additional_merchant_data: None, header_payload: None, + connector_mandate_request_reference_id: None, + authentication_id: None, + psd2_sca_exemption_type: None, } } #[actix_web::test] - async fn payments_create_success() { let conf = Settings::new().unwrap(); let tx: oneshot::Sender<()> = oneshot::channel().0; @@ -214,7 +218,10 @@ async fn payments_create_success() { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); use router::connector::Aci; @@ -261,7 +268,10 @@ async fn payments_create_failure() { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let connector = utils::construct_connector_data_old( Box::new(Aci::new()), @@ -287,6 +297,7 @@ async fn payments_create_failure() { card_issuing_country: None, bank_code: None, nick_name: Some(Secret::new("nick_name".into())), + card_holder_name: Some(Secret::new("card holder name".into())), }); let response = services::api::execute_connector_processing_step( @@ -304,7 +315,6 @@ async fn payments_create_failure() { } #[actix_web::test] - async fn refund_for_successful_payments() { let conf = Settings::new().unwrap(); use router::connector::Aci; @@ -324,7 +334,10 @@ async fn refund_for_successful_payments() { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let connector_integration: services::BoxedPaymentConnectorIntegrationInterface< types::api::Authorize, @@ -394,7 +407,10 @@ async fn refunds_create_failure() { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let connector_integration: services::BoxedRefundConnectorIntegrationInterface< types::api::Execute, diff --git a/crates/router/tests/connectors/adyen.rs b/crates/router/tests/connectors/adyen.rs index 2e8d4ab6862a..c7ad3b69a320 100644 --- a/crates/router/tests/connectors/adyen.rs +++ b/crates/router/tests/connectors/adyen.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use api_models::payments::{Address, AddressDetails, PhoneDetails}; +use hyperswitch_domain_models::address::{Address, AddressDetails, PhoneDetails}; use masking::Secret; use router::types::{self, storage::enums, PaymentAddress}; @@ -153,6 +153,7 @@ impl AdyenTest { card_issuing_country: None, bank_code: None, nick_name: Some(Secret::new("nick_name".into())), + card_holder_name: Some(Secret::new("card holder name".into())), }), confirm: true, statement_descriptor_suffix: None, diff --git a/crates/router/tests/connectors/airwallex.rs b/crates/router/tests/connectors/airwallex.rs index f46a0cf7597f..0e538818d50a 100644 --- a/crates/router/tests/connectors/airwallex.rs +++ b/crates/router/tests/connectors/airwallex.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use api_models::payments::{Address, AddressDetails}; +use hyperswitch_domain_models::address::{Address, AddressDetails}; use masking::{PeekInterface, Secret}; use router::types::{self, domain, storage::enums, AccessToken}; @@ -82,6 +82,7 @@ fn payment_method_details() -> Option { card_issuing_country: None, bank_code: None, nick_name: Some(Secret::new("nick_name".into())), + card_holder_name: Some(Secret::new("card holder name".into())), }), capture_method: Some(diesel_models::enums::CaptureMethod::Manual), router_return_url: Some("https://google.com".to_string()), diff --git a/crates/router/tests/connectors/amazonpay.rs b/crates/router/tests/connectors/amazonpay.rs new file mode 100644 index 000000000000..36ebb3178f4a --- /dev/null +++ b/crates/router/tests/connectors/amazonpay.rs @@ -0,0 +1,421 @@ +use hyperswitch_domain_models::payment_method_data::{Card, PaymentMethodData}; +use masking::Secret; +use router::types::{self, api, storage::enums}; +use test_utils::connector_auth; + +use crate::utils::{self, ConnectorActions}; + +#[derive(Clone, Copy)] +struct AmazonpayTest; +impl ConnectorActions for AmazonpayTest {} +impl utils::Connector for AmazonpayTest { + fn get_data(&self) -> api::ConnectorData { + use router::connector::Amazonpay; + utils::construct_connector_data_old( + Box::new(Amazonpay::new()), + types::Connector::Plaid, + api::GetToken::Connector, + None, + ) + } + + fn get_auth_token(&self) -> types::ConnectorAuthType { + utils::to_connector_auth_type( + connector_auth::ConnectorAuthentication::new() + .amazonpay + .expect("Missing connector authentication configuration") + .into(), + ) + } + + fn get_name(&self) -> String { + "amazonpay".to_string() + } +} + +static CONNECTOR: AmazonpayTest = AmazonpayTest {}; + +fn get_default_payment_info() -> Option { + None +} + +fn payment_method_details() -> Option { + None +} + +// Cards Positive Tests +// Creates a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_only_authorize_payment() { + let response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized); +} + +// Captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info()) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Partially captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment( + payment_method_details(), + Some(types::PaymentsCaptureData { + amount_to_capture: 50, + ..utils::PaymentCaptureType::default().0 + }), + get_default_payment_info(), + ) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_authorized_payment() { + let authorize_response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("PSync response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized,); +} + +// Voids a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_void_authorized_payment() { + let response = CONNECTOR + .authorize_and_void_payment( + payment_method_details(), + Some(types::PaymentsCancelData { + connector_transaction_id: String::from(""), + cancellation_reason: Some("requested_by_customer".to_string()), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("Void payment response"); + assert_eq!(response.status, enums::AttemptStatus::Voided); +} + +// Refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Synchronizes a refund using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_manually_captured_refund() { + let refund_response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_make_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_auto_captured_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Charged, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + capture_method: Some(enums::CaptureMethod::Automatic), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!(response.status, enums::AttemptStatus::Charged,); +} + +// Refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_auto_captured_payment() { + let response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_succeeded_payment() { + let refund_response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + refund_response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_succeeded_payment_multiple_times() { + CONNECTOR + .make_payment_and_multiple_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await; +} + +// Synchronizes a refund using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_refund() { + let refund_response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Cards Negative scenarios +// Creates a payment with incorrect CVC. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_cvc() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_cvc: Secret::new("12345".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's security code is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry month. +#[actix_web::test] +async fn should_fail_payment_for_invalid_exp_month() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_month: Secret::new("20".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration month is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry year. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_expiry_year() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_year: Secret::new("2000".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration year is invalid.".to_string(), + ); +} + +// Voids a payment using automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_fail_void_payment_for_auto_capture() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let void_response = CONNECTOR + .void_payment(txn_id.unwrap(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + void_response.response.unwrap_err().message, + "You cannot cancel this PaymentIntent because it has a status of succeeded." + ); +} + +// Captures a payment using invalid connector payment id. +#[actix_web::test] +async fn should_fail_capture_for_invalid_payment() { + let capture_response = CONNECTOR + .capture_payment("123456789".to_string(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + capture_response.response.unwrap_err().message, + String::from("No such payment_intent: '123456789'") + ); +} + +// Refunds a payment with refund amount higher than payment amount. +#[actix_web::test] +async fn should_fail_for_refund_amount_higher_than_payment_amount() { + let response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 150, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Refund amount (₹1.50) is greater than charge amount (₹1.00)", + ); +} + +// Connector dependent test cases goes here + +// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests diff --git a/crates/router/tests/connectors/bitpay.rs b/crates/router/tests/connectors/bitpay.rs index 43959fa683cb..70af3b752127 100644 --- a/crates/router/tests/connectors/bitpay.rs +++ b/crates/router/tests/connectors/bitpay.rs @@ -1,3 +1,4 @@ +use hyperswitch_domain_models::address::{Address, AddressDetails, PhoneDetails}; use masking::Secret; use router::types::{self, api, domain, storage::enums, PaymentAddress}; @@ -40,8 +41,8 @@ fn get_default_payment_info() -> Option { Some(utils::PaymentInfo { address: Some(PaymentAddress::new( None, - Some(api::Address { - address: Some(api::AddressDetails { + Some(Address { + address: Some(AddressDetails { first_name: Some(Secret::new("first".to_string())), last_name: Some(Secret::new("last".to_string())), line1: Some(Secret::new("line1".to_string())), @@ -51,7 +52,7 @@ fn get_default_payment_info() -> Option { country: Some(api_models::enums::CountryAlpha2::IN), ..Default::default() }), - phone: Some(api::PhoneDetails { + phone: Some(PhoneDetails { number: Some(Secret::new("9123456789".to_string())), country_code: Some("+91".to_string()), }), diff --git a/crates/router/tests/connectors/bluesnap.rs b/crates/router/tests/connectors/bluesnap.rs index 73799693d74b..d6df20d7ee0e 100644 --- a/crates/router/tests/connectors/bluesnap.rs +++ b/crates/router/tests/connectors/bluesnap.rs @@ -1,7 +1,7 @@ use std::str::FromStr; -use api_models::payments::{Address, AddressDetails}; use common_utils::pii::Email; +use hyperswitch_domain_models::address::{Address, AddressDetails}; use masking::Secret; use router::types::{self, domain, storage::enums, ConnectorAuthType, PaymentAddress}; diff --git a/crates/router/tests/connectors/cashtocode.rs b/crates/router/tests/connectors/cashtocode.rs index 7656cd67de51..84caf9ad7657 100644 --- a/crates/router/tests/connectors/cashtocode.rs +++ b/crates/router/tests/connectors/cashtocode.rs @@ -1,5 +1,5 @@ -use api_models::payments::{Address, AddressDetails}; use common_utils::id_type; +use hyperswitch_domain_models::address::{Address, AddressDetails}; use router::types::{self, domain, storage::enums}; use crate::{ @@ -93,7 +93,6 @@ impl CashtocodeTest { None, None, )), - return_url: Some("https://google.com".to_owned()), ..Default::default() }) } diff --git a/crates/router/tests/connectors/coinbase.rs b/crates/router/tests/connectors/coinbase.rs index 20be2e502baf..7320b875ca25 100644 --- a/crates/router/tests/connectors/coinbase.rs +++ b/crates/router/tests/connectors/coinbase.rs @@ -1,3 +1,4 @@ +use hyperswitch_domain_models::address::{Address, AddressDetails, PhoneDetails}; use masking::Secret; use router::types::{self, api, domain, storage::enums, PaymentAddress}; use serde_json::json; @@ -41,8 +42,8 @@ fn get_default_payment_info() -> Option { Some(utils::PaymentInfo { address: Some(PaymentAddress::new( None, - Some(api::Address { - address: Some(api::AddressDetails { + Some(Address { + address: Some(AddressDetails { first_name: Some(Secret::new("first".to_string())), last_name: Some(Secret::new("last".to_string())), line1: Some(Secret::new("line1".to_string())), @@ -52,7 +53,7 @@ fn get_default_payment_info() -> Option { country: Some(api_models::enums::CountryAlpha2::IN), ..Default::default() }), - phone: Some(api::PhoneDetails { + phone: Some(PhoneDetails { number: Some(Secret::new("9123456789".to_string())), country_code: Some("+91".to_string()), }), diff --git a/crates/router/tests/connectors/cryptopay.rs b/crates/router/tests/connectors/cryptopay.rs index b5f57f7d6f5c..58fcdc6dae2c 100644 --- a/crates/router/tests/connectors/cryptopay.rs +++ b/crates/router/tests/connectors/cryptopay.rs @@ -1,3 +1,4 @@ +use hyperswitch_domain_models::address::{Address, AddressDetails, PhoneDetails}; use masking::Secret; use router::types::{self, api, domain, storage::enums, PaymentAddress}; @@ -40,8 +41,8 @@ fn get_default_payment_info() -> Option { Some(utils::PaymentInfo { address: Some(PaymentAddress::new( None, - Some(api::Address { - address: Some(api::AddressDetails { + Some(Address { + address: Some(AddressDetails { first_name: Some(Secret::new("first".to_string())), last_name: Some(Secret::new("last".to_string())), line1: Some(Secret::new("line1".to_string())), @@ -51,7 +52,7 @@ fn get_default_payment_info() -> Option { country: Some(api_models::enums::CountryAlpha2::IN), ..Default::default() }), - phone: Some(api::PhoneDetails { + phone: Some(PhoneDetails { number: Some(Secret::new("9123456789".to_string())), country_code: Some("+91".to_string()), }), @@ -60,7 +61,6 @@ fn get_default_payment_info() -> Option { None, None, )), - return_url: Some(String::from("https://google.com")), ..Default::default() }) } diff --git a/crates/router/tests/connectors/cybersource.rs b/crates/router/tests/connectors/cybersource.rs index 0116b52f4edc..08001ddd782c 100644 --- a/crates/router/tests/connectors/cybersource.rs +++ b/crates/router/tests/connectors/cybersource.rs @@ -1,6 +1,7 @@ use std::str::FromStr; use common_utils::pii::Email; +use hyperswitch_domain_models::address::{Address, AddressDetails, PhoneDetails}; use masking::Secret; use router::types::{self, api, domain, storage::enums}; @@ -14,7 +15,7 @@ impl utils::Connector for Cybersource { fn get_data(&self) -> api::ConnectorData { use router::connector::Cybersource; utils::construct_connector_data_old( - Box::new(&Cybersource), + Box::new(Cybersource::new()), types::Connector::Cybersource, api::GetToken::Connector, None, @@ -37,8 +38,8 @@ fn get_default_payment_info() -> Option { Some(utils::PaymentInfo { address: Some(types::PaymentAddress::new( None, - Some(api::Address { - address: Some(api::AddressDetails { + Some(Address { + address: Some(AddressDetails { first_name: Some(Secret::new("first".to_string())), last_name: Some(Secret::new("last".to_string())), line1: Some(Secret::new("line1".to_string())), @@ -48,7 +49,7 @@ fn get_default_payment_info() -> Option { country: Some(api_models::enums::CountryAlpha2::IN), ..Default::default() }), - phone: Some(api::PhoneDetails { + phone: Some(PhoneDetails { number: Some(Secret::new("9123456789".to_string())), country_code: Some("+91".to_string()), }), diff --git a/crates/router/tests/connectors/digitalvirgo.rs b/crates/router/tests/connectors/digitalvirgo.rs new file mode 100644 index 000000000000..422c95e383a8 --- /dev/null +++ b/crates/router/tests/connectors/digitalvirgo.rs @@ -0,0 +1,402 @@ +use masking::Secret; +use router::{ + types::{self, api, storage::enums, +}}; + +use crate::utils::{self, ConnectorActions}; +use test_utils::connector_auth; + +#[derive(Clone, Copy)] +struct DigitalvirgoTest; +impl ConnectorActions for DigitalvirgoTest {} +impl utils::Connector for DigitalvirgoTest { + fn get_data(&self) -> api::ConnectorData { + use router::connector::Digitalvirgo; + api::ConnectorData { + connector: Box::new(Digitalvirgo::new()), + connector_name: types::Connector::Digitalvirgo, + get_token: types::api::GetToken::Connector, + merchant_connector_id: None, + } + } + + fn get_auth_token(&self) -> types::ConnectorAuthType { + utils::to_connector_auth_type( + connector_auth::ConnectorAuthentication::new() + .digitalvirgo + .expect("Missing connector authentication configuration").into(), + ) + } + + fn get_name(&self) -> String { + "digitalvirgo".to_string() + } +} + +static CONNECTOR: DigitalvirgoTest = DigitalvirgoTest {}; + +fn get_default_payment_info() -> Option { + None +} + +fn payment_method_details() -> Option { + None +} + +// Cards Positive Tests +// Creates a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_only_authorize_payment() { + let response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized); +} + +// Captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info()) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Partially captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment( + payment_method_details(), + Some(types::PaymentsCaptureData { + amount_to_capture: 50, + ..utils::PaymentCaptureType::default().0 + }), + get_default_payment_info(), + ) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_authorized_payment() { + let authorize_response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("PSync response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized,); +} + +// Voids a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_void_authorized_payment() { + let response = CONNECTOR + .authorize_and_void_payment( + payment_method_details(), + Some(types::PaymentsCancelData { + connector_transaction_id: String::from(""), + cancellation_reason: Some("requested_by_customer".to_string()), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("Void payment response"); + assert_eq!(response.status, enums::AttemptStatus::Voided); +} + +// Refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund(payment_method_details(), None, None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Synchronizes a refund using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_manually_captured_refund() { + let refund_response = CONNECTOR + .capture_payment_and_refund(payment_method_details(), None, None, get_default_payment_info()) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_make_payment() { + let authorize_response = CONNECTOR.make_payment(payment_method_details(), get_default_payment_info()).await.unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_auto_captured_payment() { + let authorize_response = CONNECTOR.make_payment(payment_method_details(), get_default_payment_info()).await.unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Charged, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + capture_method: Some(enums::CaptureMethod::Automatic), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!(response.status, enums::AttemptStatus::Charged,); +} + +// Refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_auto_captured_payment() { + let response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_succeeded_payment() { + let refund_response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + refund_response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_succeeded_payment_multiple_times() { + CONNECTOR + .make_payment_and_multiple_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await; +} + +// Synchronizes a refund using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_refund() { + let refund_response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Cards Negative scenarios +// Creates a payment with incorrect CVC. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_cvc() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: types::api::PaymentMethodData::Card(api::Card { + card_cvc: Secret::new("12345".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's security code is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry month. +#[actix_web::test] +async fn should_fail_payment_for_invalid_exp_month() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: api::PaymentMethodData::Card(api::Card { + card_exp_month: Secret::new("20".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration month is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry year. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_expiry_year() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: api::PaymentMethodData::Card(api::Card { + card_exp_year: Secret::new("2000".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration year is invalid.".to_string(), + ); +} + +// Voids a payment using automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_fail_void_payment_for_auto_capture() { + let authorize_response = CONNECTOR.make_payment(payment_method_details(), get_default_payment_info()).await.unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let void_response = CONNECTOR + .void_payment(txn_id.unwrap(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + void_response.response.unwrap_err().message, + "You cannot cancel this PaymentIntent because it has a status of succeeded." + ); +} + +// Captures a payment using invalid connector payment id. +#[actix_web::test] +async fn should_fail_capture_for_invalid_payment() { + let capture_response = CONNECTOR + .capture_payment("123456789".to_string(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + capture_response.response.unwrap_err().message, + String::from("No such payment_intent: '123456789'") + ); +} + +// Refunds a payment with refund amount higher than payment amount. +#[actix_web::test] +async fn should_fail_for_refund_amount_higher_than_payment_amount() { + let response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 150, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Refund amount (₹1.50) is greater than charge amount (₹1.00)", + ); +} + +// Connector dependent test cases goes here + +// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests diff --git a/crates/router/tests/connectors/dlocal.rs b/crates/router/tests/connectors/dlocal.rs index 62278d30ca75..3022f1f0b1d8 100644 --- a/crates/router/tests/connectors/dlocal.rs +++ b/crates/router/tests/connectors/dlocal.rs @@ -2,7 +2,7 @@ use std::str::FromStr; -use api_models::payments::Address; +use hyperswitch_domain_models::address::{Address, AddressDetails}; use masking::Secret; use router::types::{self, api, domain, storage::enums, PaymentAddress}; @@ -426,7 +426,7 @@ pub fn get_payment_info() -> PaymentInfo { None, Some(Address { phone: None, - address: Some(api::AddressDetails { + address: Some(AddressDetails { city: None, country: Some(api_models::enums::CountryAlpha2::PA), line1: None, diff --git a/crates/router/tests/connectors/elavon.rs b/crates/router/tests/connectors/elavon.rs new file mode 100644 index 000000000000..af00ab909a1d --- /dev/null +++ b/crates/router/tests/connectors/elavon.rs @@ -0,0 +1,427 @@ +use hyperswitch_domain_models::payment_method_data::{Card, PaymentMethodData}; +use masking::Secret; +use router::types::{self, api, storage::enums}; +use test_utils::connector_auth; + +use crate::utils::{self, ConnectorActions}; + +#[derive(Clone, Copy)] +struct ElavonTest; +impl ConnectorActions for ElavonTest {} +impl utils::Connector for ElavonTest { + fn get_data(&self) -> api::ConnectorData { + use router::connector::Elavon; + utils::construct_connector_data_old( + Box::new(Elavon::new()), + types::Connector::Plaid, + api::GetToken::Connector, + None, + ) + // api::ConnectorData { + // connector: Box::new(Elavon::new()), + // connector_name: types::Connector::Elavon, + // get_token: types::api::GetToken::Connector, + // merchant_connector_id: None, + // } + } + + fn get_auth_token(&self) -> types::ConnectorAuthType { + utils::to_connector_auth_type( + connector_auth::ConnectorAuthentication::new() + .elavon + .expect("Missing connector authentication configuration") + .into(), + ) + } + + fn get_name(&self) -> String { + "elavon".to_string() + } +} + +static CONNECTOR: ElavonTest = ElavonTest {}; + +fn get_default_payment_info() -> Option { + None +} + +fn payment_method_details() -> Option { + None +} + +// Cards Positive Tests +// Creates a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_only_authorize_payment() { + let response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized); +} + +// Captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info()) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Partially captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment( + payment_method_details(), + Some(types::PaymentsCaptureData { + amount_to_capture: 50, + ..utils::PaymentCaptureType::default().0 + }), + get_default_payment_info(), + ) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_authorized_payment() { + let authorize_response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("PSync response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized,); +} + +// Voids a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_void_authorized_payment() { + let response = CONNECTOR + .authorize_and_void_payment( + payment_method_details(), + Some(types::PaymentsCancelData { + connector_transaction_id: String::from(""), + cancellation_reason: Some("requested_by_customer".to_string()), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("Void payment response"); + assert_eq!(response.status, enums::AttemptStatus::Voided); +} + +// Refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Synchronizes a refund using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_manually_captured_refund() { + let refund_response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_make_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_auto_captured_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Charged, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + capture_method: Some(enums::CaptureMethod::Automatic), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!(response.status, enums::AttemptStatus::Charged,); +} + +// Refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_auto_captured_payment() { + let response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_succeeded_payment() { + let refund_response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + refund_response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_succeeded_payment_multiple_times() { + CONNECTOR + .make_payment_and_multiple_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await; +} + +// Synchronizes a refund using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_refund() { + let refund_response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Cards Negative scenarios +// Creates a payment with incorrect CVC. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_cvc() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_cvc: Secret::new("12345".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's security code is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry month. +#[actix_web::test] +async fn should_fail_payment_for_invalid_exp_month() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_month: Secret::new("20".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration month is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry year. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_expiry_year() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_year: Secret::new("2000".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration year is invalid.".to_string(), + ); +} + +// Voids a payment using automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_fail_void_payment_for_auto_capture() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let void_response = CONNECTOR + .void_payment(txn_id.unwrap(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + void_response.response.unwrap_err().message, + "You cannot cancel this PaymentIntent because it has a status of succeeded." + ); +} + +// Captures a payment using invalid connector payment id. +#[actix_web::test] +async fn should_fail_capture_for_invalid_payment() { + let capture_response = CONNECTOR + .capture_payment("123456789".to_string(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + capture_response.response.unwrap_err().message, + String::from("No such payment_intent: '123456789'") + ); +} + +// Refunds a payment with refund amount higher than payment amount. +#[actix_web::test] +async fn should_fail_for_refund_amount_higher_than_payment_amount() { + let response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 150, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Refund amount (₹1.50) is greater than charge amount (₹1.00)", + ); +} + +// Connector dependent test cases goes here + +// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests diff --git a/crates/router/tests/connectors/fiserv.rs b/crates/router/tests/connectors/fiserv.rs index bfa16d74f548..7f13de310044 100644 --- a/crates/router/tests/connectors/fiserv.rs +++ b/crates/router/tests/connectors/fiserv.rs @@ -53,6 +53,7 @@ fn payment_method_details() -> Option { card_issuing_country: None, bank_code: None, nick_name: Some(Secret::new("nick_name".into())), + card_holder_name: Some(Secret::new("card holder name".into())), }), capture_method: Some(diesel_models::enums::CaptureMethod::Manual), ..utils::PaymentAuthorizeType::default().0 diff --git a/crates/router/tests/connectors/forte.rs b/crates/router/tests/connectors/forte.rs index fa084fc4b2c7..3c5cde492c0c 100644 --- a/crates/router/tests/connectors/forte.rs +++ b/crates/router/tests/connectors/forte.rs @@ -2,6 +2,7 @@ use std::{str::FromStr, time::Duration}; use cards::CardNumber; use common_utils::types::MinorUnit; +use hyperswitch_domain_models::address::{Address, AddressDetails, PhoneDetails}; use masking::Secret; use router::types::{self, api, domain, storage::enums}; @@ -54,8 +55,8 @@ fn get_default_payment_info() -> Option { Some(utils::PaymentInfo { address: Some(types::PaymentAddress::new( None, - Some(api::Address { - address: Some(api::AddressDetails { + Some(Address { + address: Some(AddressDetails { first_name: Some(Secret::new("first".to_string())), last_name: Some(Secret::new("last".to_string())), line1: Some(Secret::new("line1".to_string())), @@ -65,7 +66,7 @@ fn get_default_payment_info() -> Option { country: Some(api_models::enums::CountryAlpha2::IN), ..Default::default() }), - phone: Some(api::PhoneDetails { + phone: Some(PhoneDetails { number: Some(Secret::new("9123456789".to_string())), country_code: Some("+91".to_string()), }), diff --git a/crates/router/tests/connectors/globalpay.rs b/crates/router/tests/connectors/globalpay.rs index 8879631d1e3d..cd7e6c6b7991 100644 --- a/crates/router/tests/connectors/globalpay.rs +++ b/crates/router/tests/connectors/globalpay.rs @@ -1,5 +1,6 @@ use std::str::FromStr; +use hyperswitch_domain_models::address::{Address, AddressDetails}; use masking::Secret; use router::types::{self, api, domain, storage::enums, AccessToken, ConnectorAuthType}; use serde_json::json; @@ -59,8 +60,8 @@ impl Globalpay { Some(PaymentInfo { address: Some(types::PaymentAddress::new( None, - Some(api::Address { - address: Some(api::AddressDetails { + Some(Address { + address: Some(AddressDetails { country: Some(api_models::enums::CountryAlpha2::US), ..Default::default() }), diff --git a/crates/router/tests/connectors/iatapay.rs b/crates/router/tests/connectors/iatapay.rs index 577da6ce6852..89fc270e6a2a 100644 --- a/crates/router/tests/connectors/iatapay.rs +++ b/crates/router/tests/connectors/iatapay.rs @@ -1,3 +1,4 @@ +use hyperswitch_domain_models::address::{Address, AddressDetails, PhoneDetails}; use masking::Secret; use router::types::{self, api, storage::enums, AccessToken}; @@ -57,8 +58,8 @@ fn get_default_payment_info() -> Option { Some(utils::PaymentInfo { address: Some(types::PaymentAddress::new( None, - Some(api::Address { - address: Some(api::AddressDetails { + Some(Address { + address: Some(AddressDetails { first_name: Some(Secret::new("first".to_string())), last_name: Some(Secret::new("last".to_string())), line1: Some(Secret::new("line1".to_string())), @@ -68,7 +69,7 @@ fn get_default_payment_info() -> Option { country: Some(api_models::enums::CountryAlpha2::NL), ..Default::default() }), - phone: Some(api::PhoneDetails { + phone: Some(PhoneDetails { number: Some(Secret::new("9123456789".to_string())), country_code: Some("+91".to_string()), }), @@ -78,7 +79,6 @@ fn get_default_payment_info() -> Option { None, )), access_token: get_access_token(), - return_url: Some(String::from("https://hyperswitch.io")), ..Default::default() }) } diff --git a/crates/router/tests/connectors/inespay.rs b/crates/router/tests/connectors/inespay.rs new file mode 100644 index 000000000000..6fb8914aec70 --- /dev/null +++ b/crates/router/tests/connectors/inespay.rs @@ -0,0 +1,421 @@ +use hyperswitch_domain_models::payment_method_data::{Card, PaymentMethodData}; +use masking::Secret; +use router::types::{self, api, storage::enums}; +use test_utils::connector_auth; + +use crate::utils::{self, ConnectorActions}; + +#[derive(Clone, Copy)] +struct InespayTest; +impl ConnectorActions for InespayTest {} +impl utils::Connector for InespayTest { + fn get_data(&self) -> api::ConnectorData { + use router::connector::Inespay; + utils::construct_connector_data_old( + Box::new(Inespay::new()), + types::Connector::Plaid, + api::GetToken::Connector, + None, + ) + } + + fn get_auth_token(&self) -> types::ConnectorAuthType { + utils::to_connector_auth_type( + connector_auth::ConnectorAuthentication::new() + .inespay + .expect("Missing connector authentication configuration") + .into(), + ) + } + + fn get_name(&self) -> String { + "inespay".to_string() + } +} + +static CONNECTOR: InespayTest = InespayTest {}; + +fn get_default_payment_info() -> Option { + None +} + +fn payment_method_details() -> Option { + None +} + +// Cards Positive Tests +// Creates a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_only_authorize_payment() { + let response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized); +} + +// Captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info()) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Partially captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment( + payment_method_details(), + Some(types::PaymentsCaptureData { + amount_to_capture: 50, + ..utils::PaymentCaptureType::default().0 + }), + get_default_payment_info(), + ) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_authorized_payment() { + let authorize_response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("PSync response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized,); +} + +// Voids a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_void_authorized_payment() { + let response = CONNECTOR + .authorize_and_void_payment( + payment_method_details(), + Some(types::PaymentsCancelData { + connector_transaction_id: String::from(""), + cancellation_reason: Some("requested_by_customer".to_string()), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("Void payment response"); + assert_eq!(response.status, enums::AttemptStatus::Voided); +} + +// Refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Synchronizes a refund using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_manually_captured_refund() { + let refund_response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_make_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_auto_captured_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Charged, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + capture_method: Some(enums::CaptureMethod::Automatic), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!(response.status, enums::AttemptStatus::Charged,); +} + +// Refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_auto_captured_payment() { + let response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_succeeded_payment() { + let refund_response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + refund_response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_succeeded_payment_multiple_times() { + CONNECTOR + .make_payment_and_multiple_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await; +} + +// Synchronizes a refund using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_refund() { + let refund_response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Cards Negative scenarios +// Creates a payment with incorrect CVC. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_cvc() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_cvc: Secret::new("12345".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's security code is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry month. +#[actix_web::test] +async fn should_fail_payment_for_invalid_exp_month() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_month: Secret::new("20".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration month is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry year. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_expiry_year() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_year: Secret::new("2000".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration year is invalid.".to_string(), + ); +} + +// Voids a payment using automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_fail_void_payment_for_auto_capture() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let void_response = CONNECTOR + .void_payment(txn_id.unwrap(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + void_response.response.unwrap_err().message, + "You cannot cancel this PaymentIntent because it has a status of succeeded." + ); +} + +// Captures a payment using invalid connector payment id. +#[actix_web::test] +async fn should_fail_capture_for_invalid_payment() { + let capture_response = CONNECTOR + .capture_payment("123456789".to_string(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + capture_response.response.unwrap_err().message, + String::from("No such payment_intent: '123456789'") + ); +} + +// Refunds a payment with refund amount higher than payment amount. +#[actix_web::test] +async fn should_fail_for_refund_amount_higher_than_payment_amount() { + let response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 150, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Refund amount (₹1.50) is greater than charge amount (₹1.00)", + ); +} + +// Connector dependent test cases goes here + +// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests diff --git a/crates/router/tests/connectors/jpmorgan.rs b/crates/router/tests/connectors/jpmorgan.rs new file mode 100644 index 000000000000..9e364a3bbb61 --- /dev/null +++ b/crates/router/tests/connectors/jpmorgan.rs @@ -0,0 +1,427 @@ +use hyperswitch_domain_models::payment_method_data::{Card, PaymentMethodData}; +use masking::Secret; +use router::types::{self, api, storage::enums}; +use test_utils::connector_auth; + +use crate::utils::{self, ConnectorActions}; + +#[derive(Clone, Copy)] +struct JpmorganTest; +impl JpmorganTest { + #[allow(dead_code)] + fn new() -> Self { + Self + } +} +impl ConnectorActions for JpmorganTest {} +impl utils::Connector for JpmorganTest { + fn get_data(&self) -> api::ConnectorData { + use router::connector::Jpmorgan; + utils::construct_connector_data_old( + Box::new(Jpmorgan::new()), + types::Connector::Plaid, + api::GetToken::Connector, + None, + ) + } + + fn get_auth_token(&self) -> types::ConnectorAuthType { + utils::to_connector_auth_type( + connector_auth::ConnectorAuthentication::new() + .jpmorgan + .expect("Missing connector authentication configuration") + .into(), + ) + } + + fn get_name(&self) -> String { + "jpmorgan".to_string() + } +} + +static CONNECTOR: JpmorganTest = JpmorganTest {}; + +fn get_default_payment_info() -> Option { + None +} + +fn payment_method_details() -> Option { + None +} + +// Cards Positive Tests +// Creates a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_only_authorize_payment() { + let response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized); +} + +// Captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info()) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Partially captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment( + payment_method_details(), + Some(types::PaymentsCaptureData { + amount_to_capture: 50, + ..utils::PaymentCaptureType::default().0 + }), + get_default_payment_info(), + ) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_authorized_payment() { + let authorize_response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("PSync response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized,); +} + +// Voids a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_void_authorized_payment() { + let response = CONNECTOR + .authorize_and_void_payment( + payment_method_details(), + Some(types::PaymentsCancelData { + connector_transaction_id: String::from(""), + cancellation_reason: Some("requested_by_customer".to_string()), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("Void payment response"); + assert_eq!(response.status, enums::AttemptStatus::Voided); +} + +// Refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Synchronizes a refund using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_manually_captured_refund() { + let refund_response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_make_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_auto_captured_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Charged, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + capture_method: Some(enums::CaptureMethod::Automatic), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!(response.status, enums::AttemptStatus::Charged,); +} + +// Refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_auto_captured_payment() { + let response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_succeeded_payment() { + let refund_response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + refund_response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_succeeded_payment_multiple_times() { + CONNECTOR + .make_payment_and_multiple_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await; +} + +// Synchronizes a refund using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_refund() { + let refund_response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Cards Negative scenarios +// Creates a payment with incorrect CVC. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_cvc() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_cvc: Secret::new("12345".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's security code is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry month. +#[actix_web::test] +async fn should_fail_payment_for_invalid_exp_month() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_month: Secret::new("20".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration month is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry year. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_expiry_year() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_year: Secret::new("2000".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration year is invalid.".to_string(), + ); +} + +// Voids a payment using automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_fail_void_payment_for_auto_capture() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let void_response = CONNECTOR + .void_payment(txn_id.unwrap(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + void_response.response.unwrap_err().message, + "You cannot cancel this PaymentIntent because it has a status of succeeded." + ); +} + +// Captures a payment using invalid connector payment id. +#[actix_web::test] +async fn should_fail_capture_for_invalid_payment() { + let capture_response = CONNECTOR + .capture_payment("123456789".to_string(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + capture_response.response.unwrap_err().message, + String::from("No such payment_intent: '123456789'") + ); +} + +// Refunds a payment with refund amount higher than payment amount. +#[actix_web::test] +async fn should_fail_for_refund_amount_higher_than_payment_amount() { + let response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 150, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Refund amount (₹1.50) is greater than charge amount (₹1.00)", + ); +} + +// Connector dependent test cases goes here + +// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests diff --git a/crates/router/tests/connectors/main.rs b/crates/router/tests/connectors/main.rs index 433f2d03eea6..31ceb1a72456 100644 --- a/crates/router/tests/connectors/main.rs +++ b/crates/router/tests/connectors/main.rs @@ -10,6 +10,7 @@ mod aci; mod adyen; mod adyenplatform; mod airwallex; +mod amazonpay; mod authorizedotnet; mod bambora; mod bamboraapac; @@ -31,6 +32,7 @@ mod dlocal; #[cfg(feature = "dummy_connector")] mod dummyconnector; mod ebanx; +mod elavon; mod fiserv; mod fiservemea; mod fiuu; @@ -41,7 +43,9 @@ mod gocardless; mod gpayments; mod helcim; mod iatapay; +mod inespay; mod itaubank; +mod jpmorgan; mod mifinity; mod mollie; mod multisafepay; @@ -49,6 +53,7 @@ mod netcetera; mod nexinets; mod nexixpay; mod nmi; +mod nomupay; mod noon; mod novalnet; mod nuvei; @@ -69,6 +74,7 @@ mod powertranz; mod prophetpay; mod rapyd; mod razorpay; +mod redsys; mod shift4; mod square; mod stax; @@ -76,6 +82,7 @@ mod stripe; mod taxjar; mod trustpay; mod tsys; +mod unified_authentication_service; mod utils; mod volt; mod wellsfargo; diff --git a/crates/router/tests/connectors/mollie.rs b/crates/router/tests/connectors/mollie.rs index 7017363cad0f..d59bc78226dd 100644 --- a/crates/router/tests/connectors/mollie.rs +++ b/crates/router/tests/connectors/mollie.rs @@ -13,7 +13,7 @@ impl utils::Connector for MollieTest { fn get_data(&self) -> types::api::ConnectorData { use router::connector::Mollie; utils::construct_connector_data_old( - Box::new(&Mollie), + Box::new(Mollie::new()), types::Connector::Mollie, types::api::GetToken::Connector, None, diff --git a/crates/router/tests/connectors/multisafepay.rs b/crates/router/tests/connectors/multisafepay.rs index 0d5af818c358..1a81b00116fa 100644 --- a/crates/router/tests/connectors/multisafepay.rs +++ b/crates/router/tests/connectors/multisafepay.rs @@ -1,4 +1,4 @@ -use api_models::payments::{Address, AddressDetails}; +use hyperswitch_domain_models::address::{Address, AddressDetails}; use masking::Secret; use router::types::{self, domain, storage::enums, PaymentAddress}; diff --git a/crates/router/tests/connectors/nomupay.rs b/crates/router/tests/connectors/nomupay.rs new file mode 100644 index 000000000000..dbf892b3b046 --- /dev/null +++ b/crates/router/tests/connectors/nomupay.rs @@ -0,0 +1,421 @@ +use hyperswitch_domain_models::payment_method_data::{Card, PaymentMethodData}; +use masking::Secret; +use router::types::{self, api, storage::enums}; +use test_utils::connector_auth; + +use crate::utils::{self, ConnectorActions}; + +#[derive(Clone, Copy)] +struct NomupayTest; +impl ConnectorActions for NomupayTest {} +impl utils::Connector for NomupayTest { + fn get_data(&self) -> api::ConnectorData { + use router::connector::Nomupay; + utils::construct_connector_data_old( + Box::new(Nomupay::new()), + types::Connector::Plaid, + api::GetToken::Connector, + None, + ) + } + + fn get_auth_token(&self) -> types::ConnectorAuthType { + utils::to_connector_auth_type( + connector_auth::ConnectorAuthentication::new() + .nomupay + .expect("Missing connector authentication configuration") + .into(), + ) + } + + fn get_name(&self) -> String { + "nomupay".to_string() + } +} + +static CONNECTOR: NomupayTest = NomupayTest {}; + +fn get_default_payment_info() -> Option { + None +} + +fn payment_method_details() -> Option { + None +} + +// Cards Positive Tests +// Creates a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_only_authorize_payment() { + let response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized); +} + +// Captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info()) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Partially captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment( + payment_method_details(), + Some(types::PaymentsCaptureData { + amount_to_capture: 50, + ..utils::PaymentCaptureType::default().0 + }), + get_default_payment_info(), + ) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_authorized_payment() { + let authorize_response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("PSync response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized,); +} + +// Voids a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_void_authorized_payment() { + let response = CONNECTOR + .authorize_and_void_payment( + payment_method_details(), + Some(types::PaymentsCancelData { + connector_transaction_id: String::from(""), + cancellation_reason: Some("requested_by_customer".to_string()), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("Void payment response"); + assert_eq!(response.status, enums::AttemptStatus::Voided); +} + +// Refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Synchronizes a refund using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_manually_captured_refund() { + let refund_response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_make_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_auto_captured_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Charged, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + capture_method: Some(enums::CaptureMethod::Automatic), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!(response.status, enums::AttemptStatus::Charged,); +} + +// Refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_auto_captured_payment() { + let response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_succeeded_payment() { + let refund_response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + refund_response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_succeeded_payment_multiple_times() { + CONNECTOR + .make_payment_and_multiple_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await; +} + +// Synchronizes a refund using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_refund() { + let refund_response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Cards Negative scenarios +// Creates a payment with incorrect CVC. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_cvc() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_cvc: Secret::new("12345".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's security code is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry month. +#[actix_web::test] +async fn should_fail_payment_for_invalid_exp_month() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_month: Secret::new("20".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration month is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry year. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_expiry_year() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_year: Secret::new("2000".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration year is invalid.".to_string(), + ); +} + +// Voids a payment using automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_fail_void_payment_for_auto_capture() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let void_response = CONNECTOR + .void_payment(txn_id.unwrap(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + void_response.response.unwrap_err().message, + "You cannot cancel this PaymentIntent because it has a status of succeeded." + ); +} + +// Captures a payment using invalid connector payment id. +#[actix_web::test] +async fn should_fail_capture_for_invalid_payment() { + let capture_response = CONNECTOR + .capture_payment("123456789".to_string(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + capture_response.response.unwrap_err().message, + String::from("No such payment_intent: '123456789'") + ); +} + +// Refunds a payment with refund amount higher than payment amount. +#[actix_web::test] +async fn should_fail_for_refund_amount_higher_than_payment_amount() { + let response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 150, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Refund amount (₹1.50) is greater than charge amount (₹1.00)", + ); +} + +// Connector dependent test cases goes here + +// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests diff --git a/crates/router/tests/connectors/opayo.rs b/crates/router/tests/connectors/opayo.rs index 6bca86fa8130..585a6f4486bf 100644 --- a/crates/router/tests/connectors/opayo.rs +++ b/crates/router/tests/connectors/opayo.rs @@ -15,7 +15,7 @@ impl utils::Connector for OpayoTest { fn get_data(&self) -> types::api::ConnectorData { use router::connector::Opayo; utils::construct_connector_data_old( - Box::new(&Opayo), + Box::new(Opayo::new()), // Remove `dummy_connector` feature gate from module in `main.rs` when updating this to use actual connector variant types::Connector::DummyConnector1, types::api::GetToken::Connector, diff --git a/crates/router/tests/connectors/opennode.rs b/crates/router/tests/connectors/opennode.rs index e8778f9f4233..6605ea46c328 100644 --- a/crates/router/tests/connectors/opennode.rs +++ b/crates/router/tests/connectors/opennode.rs @@ -1,3 +1,4 @@ +use hyperswitch_domain_models::address::{Address, AddressDetails, PhoneDetails}; use masking::Secret; use router::types::{self, api, domain, storage::enums}; @@ -40,8 +41,8 @@ fn get_default_payment_info() -> Option { Some(utils::PaymentInfo { address: Some(types::PaymentAddress::new( None, - Some(api::Address { - address: Some(api::AddressDetails { + Some(Address { + address: Some(AddressDetails { first_name: Some(Secret::new("first".to_string())), last_name: Some(Secret::new("last".to_string())), line1: Some(Secret::new("line1".to_string())), @@ -51,7 +52,7 @@ fn get_default_payment_info() -> Option { country: Some(api_models::enums::CountryAlpha2::IN), ..Default::default() }), - phone: Some(api::PhoneDetails { + phone: Some(PhoneDetails { number: Some(Secret::new("9123456789".to_string())), country_code: Some("+91".to_string()), }), @@ -60,7 +61,6 @@ fn get_default_payment_info() -> Option { None, None, )), - return_url: Some(String::from("https://google.com")), ..Default::default() }) } diff --git a/crates/router/tests/connectors/payeezy.rs b/crates/router/tests/connectors/payeezy.rs index 1bc81ebfe257..a91f5b0feddd 100644 --- a/crates/router/tests/connectors/payeezy.rs +++ b/crates/router/tests/connectors/payeezy.rs @@ -1,7 +1,7 @@ use std::str::FromStr; -use api_models::payments::{Address, AddressDetails}; use cards::CardNumber; +use hyperswitch_domain_models::address::{Address, AddressDetails}; use masking::Secret; use router::{ core::errors, @@ -71,7 +71,7 @@ impl PayeezyTest { ..Default::default() }) } - fn get_request_interval(&self) -> u64 { + fn get_request_interval(self) -> u64 { 20 } } diff --git a/crates/router/tests/connectors/payme.rs b/crates/router/tests/connectors/payme.rs index e85c57df622f..b56e1ec83dec 100644 --- a/crates/router/tests/connectors/payme.rs +++ b/crates/router/tests/connectors/payme.rs @@ -1,7 +1,8 @@ use std::str::FromStr; -use api_models::payments::{Address, AddressDetails, OrderDetailsWithAmount}; -use common_utils::pii::Email; +use common_utils::{pii::Email, types::MinorUnit}; +use diesel_models::types::OrderDetailsWithAmount; +use hyperswitch_domain_models::address::{Address, AddressDetails}; use masking::Secret; use router::types::{self, domain, storage::enums, PaymentAddress}; @@ -65,7 +66,6 @@ fn get_default_payment_info() -> Option { auth_type: None, access_token: None, connector_meta_data: None, - return_url: None, connector_customer: None, payment_method_token: None, #[cfg(feature = "payouts")] @@ -80,7 +80,7 @@ fn payment_method_details() -> Option { order_details: Some(vec![OrderDetailsWithAmount { product_name: "iphone 13".to_string(), quantity: 1, - amount: 1000, + amount: MinorUnit::new(1000), product_img_link: None, requires_shipping: None, product_id: None, @@ -89,6 +89,8 @@ fn payment_method_details() -> Option { brand: None, product_type: None, product_tax_code: None, + tax_rate: None, + total_tax_amount: None, }]), router_return_url: Some("https://hyperswitch.io".to_string()), webhook_url: Some("https://hyperswitch.io".to_string()), @@ -381,7 +383,7 @@ async fn should_fail_payment_for_incorrect_cvc() { order_details: Some(vec![OrderDetailsWithAmount { product_name: "iphone 13".to_string(), quantity: 1, - amount: 100, + amount: MinorUnit::new(100), product_img_link: None, requires_shipping: None, product_id: None, @@ -390,6 +392,8 @@ async fn should_fail_payment_for_incorrect_cvc() { brand: None, product_type: None, product_tax_code: None, + tax_rate: None, + total_tax_amount: None, }]), router_return_url: Some("https://hyperswitch.io".to_string()), webhook_url: Some("https://hyperswitch.io".to_string()), @@ -421,7 +425,7 @@ async fn should_fail_payment_for_invalid_exp_month() { order_details: Some(vec![OrderDetailsWithAmount { product_name: "iphone 13".to_string(), quantity: 1, - amount: 100, + amount: MinorUnit::new(100), product_img_link: None, requires_shipping: None, product_id: None, @@ -430,6 +434,8 @@ async fn should_fail_payment_for_invalid_exp_month() { brand: None, product_type: None, product_tax_code: None, + tax_rate: None, + total_tax_amount: None, }]), router_return_url: Some("https://hyperswitch.io".to_string()), webhook_url: Some("https://hyperswitch.io".to_string()), @@ -461,7 +467,7 @@ async fn should_fail_payment_for_incorrect_expiry_year() { order_details: Some(vec![OrderDetailsWithAmount { product_name: "iphone 13".to_string(), quantity: 1, - amount: 100, + amount: MinorUnit::new(100), product_img_link: None, requires_shipping: None, product_id: None, @@ -470,6 +476,8 @@ async fn should_fail_payment_for_incorrect_expiry_year() { brand: None, product_type: None, product_tax_code: None, + tax_rate: None, + total_tax_amount: None, }]), router_return_url: Some("https://hyperswitch.io".to_string()), webhook_url: Some("https://hyperswitch.io".to_string()), diff --git a/crates/router/tests/connectors/payu.rs b/crates/router/tests/connectors/payu.rs index b0e5e9ec6a1d..9bb3785bbd6a 100644 --- a/crates/router/tests/connectors/payu.rs +++ b/crates/router/tests/connectors/payu.rs @@ -11,7 +11,7 @@ impl Connector for Payu { fn get_data(&self) -> types::api::ConnectorData { use router::connector::Payu; utils::construct_connector_data_old( - Box::new(&Payu), + Box::new(Payu::new()), types::Connector::Payu, types::api::GetToken::Connector, None, diff --git a/crates/router/tests/connectors/rapyd.rs b/crates/router/tests/connectors/rapyd.rs index fe6bc34cf18f..ee6ac303e88e 100644 --- a/crates/router/tests/connectors/rapyd.rs +++ b/crates/router/tests/connectors/rapyd.rs @@ -16,7 +16,7 @@ impl utils::Connector for Rapyd { fn get_data(&self) -> types::api::ConnectorData { use router::connector::Rapyd; utils::construct_connector_data_old( - Box::new(&Rapyd), + Box::new(Rapyd::new()), types::Connector::Rapyd, types::api::GetToken::Connector, None, @@ -53,6 +53,7 @@ async fn should_only_authorize_payment() { card_issuing_country: None, bank_code: None, nick_name: Some(Secret::new("nick_name".into())), + card_holder_name: Some(Secret::new("card holder name".into())), }), capture_method: Some(diesel_models::enums::CaptureMethod::Manual), ..utils::PaymentAuthorizeType::default().0 @@ -80,6 +81,7 @@ async fn should_authorize_and_capture_payment() { card_issuing_country: None, bank_code: None, nick_name: Some(Secret::new("nick_name".into())), + card_holder_name: Some(Secret::new("card holder name".into())), }), ..utils::PaymentAuthorizeType::default().0 }), diff --git a/crates/router/tests/connectors/redsys.rs b/crates/router/tests/connectors/redsys.rs new file mode 100644 index 000000000000..532bbb6f5509 --- /dev/null +++ b/crates/router/tests/connectors/redsys.rs @@ -0,0 +1,421 @@ +use hyperswitch_domain_models::payment_method_data::{Card, PaymentMethodData}; +use masking::Secret; +use router::types::{self, api, storage::enums}; +use test_utils::connector_auth; + +use crate::utils::{self, ConnectorActions}; + +#[derive(Clone, Copy)] +struct RedsysTest; +impl ConnectorActions for RedsysTest {} +impl utils::Connector for RedsysTest { + fn get_data(&self) -> api::ConnectorData { + use router::connector::Redsys; + utils::construct_connector_data_old( + Box::new(Redsys::new()), + types::Connector::Plaid, + api::GetToken::Connector, + None, + ) + } + + fn get_auth_token(&self) -> types::ConnectorAuthType { + utils::to_connector_auth_type( + connector_auth::ConnectorAuthentication::new() + .redsys + .expect("Missing connector authentication configuration") + .into(), + ) + } + + fn get_name(&self) -> String { + "redsys".to_string() + } +} + +static CONNECTOR: RedsysTest = RedsysTest {}; + +fn get_default_payment_info() -> Option { + None +} + +fn payment_method_details() -> Option { + None +} + +// Cards Positive Tests +// Creates a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_only_authorize_payment() { + let response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized); +} + +// Captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info()) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Partially captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment( + payment_method_details(), + Some(types::PaymentsCaptureData { + amount_to_capture: 50, + ..utils::PaymentCaptureType::default().0 + }), + get_default_payment_info(), + ) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_authorized_payment() { + let authorize_response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("PSync response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized,); +} + +// Voids a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_void_authorized_payment() { + let response = CONNECTOR + .authorize_and_void_payment( + payment_method_details(), + Some(types::PaymentsCancelData { + connector_transaction_id: String::from(""), + cancellation_reason: Some("requested_by_customer".to_string()), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("Void payment response"); + assert_eq!(response.status, enums::AttemptStatus::Voided); +} + +// Refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Synchronizes a refund using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_manually_captured_refund() { + let refund_response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_make_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_auto_captured_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Charged, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + capture_method: Some(enums::CaptureMethod::Automatic), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!(response.status, enums::AttemptStatus::Charged,); +} + +// Refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_auto_captured_payment() { + let response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_succeeded_payment() { + let refund_response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + refund_response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_succeeded_payment_multiple_times() { + CONNECTOR + .make_payment_and_multiple_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await; +} + +// Synchronizes a refund using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_refund() { + let refund_response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Cards Negative scenarios +// Creates a payment with incorrect CVC. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_cvc() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_cvc: Secret::new("12345".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's security code is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry month. +#[actix_web::test] +async fn should_fail_payment_for_invalid_exp_month() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_month: Secret::new("20".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration month is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry year. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_expiry_year() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_year: Secret::new("2000".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration year is invalid.".to_string(), + ); +} + +// Voids a payment using automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_fail_void_payment_for_auto_capture() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let void_response = CONNECTOR + .void_payment(txn_id.unwrap(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + void_response.response.unwrap_err().message, + "You cannot cancel this PaymentIntent because it has a status of succeeded." + ); +} + +// Captures a payment using invalid connector payment id. +#[actix_web::test] +async fn should_fail_capture_for_invalid_payment() { + let capture_response = CONNECTOR + .capture_payment("123456789".to_string(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + capture_response.response.unwrap_err().message, + String::from("No such payment_intent: '123456789'") + ); +} + +// Refunds a payment with refund amount higher than payment amount. +#[actix_web::test] +async fn should_fail_for_refund_amount_higher_than_payment_amount() { + let response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 150, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Refund amount (₹1.50) is greater than charge amount (₹1.00)", + ); +} + +// Connector dependent test cases goes here + +// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests diff --git a/crates/router/tests/connectors/sample_auth.toml b/crates/router/tests/connectors/sample_auth.toml index f61770a5bde5..10db11bb3b0c 100644 --- a/crates/router/tests/connectors/sample_auth.toml +++ b/crates/router/tests/connectors/sample_auth.toml @@ -10,6 +10,9 @@ api_key = "Bearer MyApiKey" key1 = "MerchantId" api_secret = "Secondary key" +[amazonpay] +api_key="API Key" + [authorizedotnet] api_key = "MyMerchantName" key1 = "MyTransactionKey" @@ -30,6 +33,7 @@ api_key = "Bearer MyApiKey" [worldpay] api_key = "api_key" key1 = "key1" +api_secret = "Merchant Identifier" [payu] api_key = "Bearer MyApiKey" @@ -264,6 +268,9 @@ api_key="API Key" [nexixpay] api_key="API Key" +[redsys] +api_key="API Key" + [wellsfargopayout] api_key = "Consumer Key" key1 = "Gateway Entity Id" @@ -282,3 +289,19 @@ api_secret = "Client Key" [thunes] api_key="API Key" + +[inespay] +api_key="API Key" + +[jpmorgan] +api_key="Client ID" +key1 ="Client Secret" + +[elavon] +api_key="API Key" + +[nomupay] +api_key="API Key" + +[unified_authentication_service] +api_key="API Key" \ No newline at end of file diff --git a/crates/router/tests/connectors/shift4.rs b/crates/router/tests/connectors/shift4.rs index fdaa1ebedefc..ce10c9dad1a6 100644 --- a/crates/router/tests/connectors/shift4.rs +++ b/crates/router/tests/connectors/shift4.rs @@ -15,7 +15,7 @@ impl utils::Connector for Shift4Test { fn get_data(&self) -> types::api::ConnectorData { use router::connector::Shift4; utils::construct_connector_data_old( - Box::new(&Shift4), + Box::new(Shift4::new()), types::Connector::Shift4, types::api::GetToken::Connector, None, diff --git a/crates/router/tests/connectors/square.rs b/crates/router/tests/connectors/square.rs index f7b633e8a152..8f5fa4a32c70 100644 --- a/crates/router/tests/connectors/square.rs +++ b/crates/router/tests/connectors/square.rs @@ -42,7 +42,6 @@ fn get_default_payment_info(payment_method_token: Option) -> Option BrowserInformation { accept_header: Some("*".to_string()), user_agent: Some("none".to_string()), ip_address: None, + os_type: None, + os_version: None, + device_model: None, } } @@ -69,8 +73,8 @@ fn get_default_payment_info() -> Option { Some(utils::PaymentInfo { address: Some(types::PaymentAddress::new( None, - Some(api::Address { - address: Some(api::AddressDetails { + Some(Address { + address: Some(AddressDetails { first_name: Some(Secret::new("first".to_string())), last_name: Some(Secret::new("last".to_string())), line1: Some(Secret::new("line1".to_string())), diff --git a/crates/router/tests/connectors/tsys.rs b/crates/router/tests/connectors/tsys.rs index 4cda0b479b24..6a4843ac2cd1 100644 --- a/crates/router/tests/connectors/tsys.rs +++ b/crates/router/tests/connectors/tsys.rs @@ -16,7 +16,7 @@ impl utils::Connector for TsysTest { fn get_data(&self) -> types::api::ConnectorData { use router::connector::Tsys; utils::construct_connector_data_old( - Box::new(&Tsys), + Box::new(Tsys::new()), types::Connector::Tsys, types::api::GetToken::Connector, None, diff --git a/crates/router/tests/connectors/unified_authentication_service.rs b/crates/router/tests/connectors/unified_authentication_service.rs new file mode 100644 index 000000000000..0a403d9d260b --- /dev/null +++ b/crates/router/tests/connectors/unified_authentication_service.rs @@ -0,0 +1,421 @@ +use hyperswitch_domain_models::payment_method_data::{Card, PaymentMethodData}; +use masking::Secret; +use router::types::{self, api, storage::enums}; +use test_utils::connector_auth; + +use crate::utils::{self, ConnectorActions}; + +#[derive(Clone, Copy)] +struct UnifiedAuthenticationServiceTest; +impl ConnectorActions for UnifiedAuthenticationServiceTest {} +impl utils::Connector for UnifiedAuthenticationServiceTest { + fn get_data(&self) -> api::ConnectorData { + use router::connector::UnifiedAuthenticationService; + utils::construct_connector_data_old( + Box::new(UnifiedAuthenticationService::new()), + types::Connector::Plaid, + api::GetToken::Connector, + None, + ) + } + + fn get_auth_token(&self) -> types::ConnectorAuthType { + utils::to_connector_auth_type( + connector_auth::ConnectorAuthentication::new() + .unified_authentication_service + .expect("Missing connector authentication configuration") + .into(), + ) + } + + fn get_name(&self) -> String { + "unified_authentication_service".to_string() + } +} + +static CONNECTOR: UnifiedAuthenticationServiceTest = UnifiedAuthenticationServiceTest {}; + +fn get_default_payment_info() -> Option { + None +} + +fn payment_method_details() -> Option { + None +} + +// Cards Positive Tests +// Creates a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_only_authorize_payment() { + let response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized); +} + +// Captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info()) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Partially captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment( + payment_method_details(), + Some(types::PaymentsCaptureData { + amount_to_capture: 50, + ..utils::PaymentCaptureType::default().0 + }), + get_default_payment_info(), + ) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_authorized_payment() { + let authorize_response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("PSync response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized,); +} + +// Voids a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_void_authorized_payment() { + let response = CONNECTOR + .authorize_and_void_payment( + payment_method_details(), + Some(types::PaymentsCancelData { + connector_transaction_id: String::from(""), + cancellation_reason: Some("requested_by_customer".to_string()), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("Void payment response"); + assert_eq!(response.status, enums::AttemptStatus::Voided); +} + +// Refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Synchronizes a refund using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_manually_captured_refund() { + let refund_response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_make_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_auto_captured_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Charged, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + capture_method: Some(enums::CaptureMethod::Automatic), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!(response.status, enums::AttemptStatus::Charged,); +} + +// Refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_auto_captured_payment() { + let response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_succeeded_payment() { + let refund_response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + refund_response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_succeeded_payment_multiple_times() { + CONNECTOR + .make_payment_and_multiple_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await; +} + +// Synchronizes a refund using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_refund() { + let refund_response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Cards Negative scenarios +// Creates a payment with incorrect CVC. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_cvc() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_cvc: Secret::new("12345".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's security code is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry month. +#[actix_web::test] +async fn should_fail_payment_for_invalid_exp_month() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_month: Secret::new("20".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration month is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry year. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_expiry_year() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_year: Secret::new("2000".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration year is invalid.".to_string(), + ); +} + +// Voids a payment using automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_fail_void_payment_for_auto_capture() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let void_response = CONNECTOR + .void_payment(txn_id.unwrap(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + void_response.response.unwrap_err().message, + "You cannot cancel this PaymentIntent because it has a status of succeeded." + ); +} + +// Captures a payment using invalid connector payment id. +#[actix_web::test] +async fn should_fail_capture_for_invalid_payment() { + let capture_response = CONNECTOR + .capture_payment("123456789".to_string(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + capture_response.response.unwrap_err().message, + String::from("No such payment_intent: '123456789'") + ); +} + +// Refunds a payment with refund amount higher than payment amount. +#[actix_web::test] +async fn should_fail_for_refund_amount_higher_than_payment_amount() { + let response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 150, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Refund amount (₹1.50) is greater than charge amount (₹1.00)", + ); +} + +// Connector dependent test cases goes here + +// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index c68032369a1d..305b11fe5b3d 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -63,7 +63,6 @@ pub struct PaymentInfo { pub auth_type: Option, pub access_token: Option, pub connector_meta_data: Option, - pub return_url: Option, pub connector_customer: Option, pub payment_method_token: Option, #[cfg(feature = "payouts")] @@ -78,8 +77,8 @@ impl PaymentInfo { address: Some(PaymentAddress::new( None, None, - Some(types::api::Address { - address: Some(types::api::AddressDetails { + Some(hyperswitch_domain_models::address::Address { + address: Some(hyperswitch_domain_models::address::AddressDetails { first_name: Some(Secret::new("John".to_string())), last_name: Some(Secret::new("Doe".to_string())), ..Default::default() @@ -404,8 +403,9 @@ pub trait ConnectorActions: Connector { reason: None, connector_refund_id: Some(refund_id), browser_info: None, - charges: None, + split_refunds: None, integrity_object: None, + refund_status: enums::RefundStatus::Pending, }), payment_info, ); @@ -502,7 +502,6 @@ pub trait ConnectorActions: Connector { payment_method: enums::PaymentMethod::Card, connector_auth_type: self.get_auth_token(), description: Some("This is a test".to_string()), - return_url: info.clone().and_then(|a| a.return_url), payment_method_status: None, request: req, response: Err(types::ErrorResponse::default()), @@ -546,6 +545,9 @@ pub trait ConnectorActions: Connector { integrity_check: Ok(()), additional_merchant_data: None, header_payload: None, + connector_mandate_request_reference_id: None, + psd2_sca_exemption_type: None, + authentication_id: None, } } @@ -567,7 +569,7 @@ pub trait ConnectorActions: Connector { Ok(types::PaymentsResponseData::MultipleCaptureResponse { .. }) => None, Ok(types::PaymentsResponseData::IncrementalAuthorizationResponse { .. }) => None, Ok(types::PaymentsResponseData::PostProcessingResponse { .. }) => None, - // Ok(types::PaymentsResponseData::SessionUpdateResponse { .. }) => None, + Ok(types::PaymentsResponseData::SessionUpdateResponse { .. }) => None, Err(_) => None, } } @@ -598,7 +600,10 @@ pub trait ConnectorActions: Connector { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let res = services::api::execute_connector_processing_step( &state, @@ -638,7 +643,10 @@ pub trait ConnectorActions: Connector { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let res = services::api::execute_connector_processing_step( &state, @@ -679,7 +687,10 @@ pub trait ConnectorActions: Connector { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let res = services::api::execute_connector_processing_step( &state, @@ -719,7 +730,10 @@ pub trait ConnectorActions: Connector { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let res = services::api::execute_connector_processing_step( &state, @@ -810,7 +824,10 @@ pub trait ConnectorActions: Connector { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let res = services::api::execute_connector_processing_step( &state, @@ -847,7 +864,10 @@ async fn call_connector< )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); services::api::execute_connector_processing_step( &state, @@ -906,6 +926,7 @@ impl Default for CCardType { card_issuing_country: None, bank_code: None, nick_name: Some(Secret::new("nick_name".into())), + card_holder_name: Some(Secret::new("card holder name".into())), }) } } @@ -916,6 +937,7 @@ impl Default for PaymentAuthorizeType { payment_method_data: types::domain::PaymentMethodData::Card(CCardType::default().0), amount: 100, minor_amount: MinorUnit::new(100), + order_tax_amount: Some(MinorUnit::zero()), currency: enums::Currency::USD, confirm: true, statement_descriptor_suffix: None, @@ -944,9 +966,11 @@ impl Default for PaymentAuthorizeType { metadata: None, authentication_data: None, customer_acceptance: None, - charges: None, + split_payments: None, integrity_object: None, merchant_order_reference_id: None, + additional_payment_method_data: None, + shipping_cost: None, }; Self(data) } @@ -987,6 +1011,9 @@ impl Default for BrowserInfoType { java_enabled: Some(true), java_script_enabled: Some(true), ip_address: Some("127.0.0.1".parse().unwrap()), + device_model: Some("Apple IPHONE 7".to_string()), + os_type: Some("IOS or ANDROID".to_string()), + os_version: Some("IOS 14.5".to_string()), }; Self(data) } @@ -1029,8 +1056,9 @@ impl Default for PaymentRefundType { reason: Some("Customer returned product".to_string()), connector_refund_id: None, browser_info: None, - charges: None, + split_refunds: None, integrity_object: None, + refund_status: enums::RefundStatus::Pending, }; Self(data) } @@ -1079,7 +1107,7 @@ pub fn get_connector_transaction_id( Ok(types::PaymentsResponseData::MultipleCaptureResponse { .. }) => None, Ok(types::PaymentsResponseData::IncrementalAuthorizationResponse { .. }) => None, Ok(types::PaymentsResponseData::PostProcessingResponse { .. }) => None, - // Ok(types::PaymentsResponseData::SessionUpdateResponse { .. }) => None, + Ok(types::PaymentsResponseData::SessionUpdateResponse { .. }) => None, Err(_) => None, } } diff --git a/crates/router/tests/connectors/wellsfargo.rs b/crates/router/tests/connectors/wellsfargo.rs index 4425c0773772..cab82c59ac85 100644 --- a/crates/router/tests/connectors/wellsfargo.rs +++ b/crates/router/tests/connectors/wellsfargo.rs @@ -11,7 +11,7 @@ impl utils::Connector for WellsfargoTest { fn get_data(&self) -> api::ConnectorData { use router::connector::Wellsfargo; utils::construct_connector_data_old( - Box::new(&Wellsfargo), + Box::new(Wellsfargo::new()), types::Connector::Wellsfargo, api::GetToken::Connector, None, diff --git a/crates/router/tests/connectors/wise.rs b/crates/router/tests/connectors/wise.rs index 761678046701..2156cfb1476c 100644 --- a/crates/router/tests/connectors/wise.rs +++ b/crates/router/tests/connectors/wise.rs @@ -1,4 +1,4 @@ -use api_models::payments::{Address, AddressDetails}; +use hyperswitch_domain_models::address::{Address, AddressDetails}; use masking::Secret; use router::{ types, @@ -28,7 +28,7 @@ impl utils::Connector for WiseTest { fn get_payout_data(&self) -> Option { use router::connector::Wise; Some(utils::construct_connector_data_old( - Box::new(&Wise), + Box::new(Wise::new()), types::Connector::Wise, api::GetToken::Connector, None, diff --git a/crates/router/tests/connectors/worldline.rs b/crates/router/tests/connectors/worldline.rs index 73e710eb2f19..793a1cc15f4a 100644 --- a/crates/router/tests/connectors/worldline.rs +++ b/crates/router/tests/connectors/worldline.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use api_models::payments::{Address, AddressDetails}; +use hyperswitch_domain_models::address::{Address, AddressDetails}; use masking::Secret; use router::{ connector::Worldline, @@ -83,6 +83,7 @@ impl WorldlineTest { card_issuing_country: None, bank_code: None, nick_name: Some(Secret::new("nick_name".into())), + card_holder_name: Some(Secret::new("card holder name".into())), }), confirm: true, statement_descriptor_suffix: None, diff --git a/crates/router/tests/connectors/worldpay.rs b/crates/router/tests/connectors/worldpay.rs index d4f6e167ff87..451aa398eb7b 100644 --- a/crates/router/tests/connectors/worldpay.rs +++ b/crates/router/tests/connectors/worldpay.rs @@ -20,7 +20,7 @@ impl utils::Connector for Worldpay { fn get_data(&self) -> types::api::ConnectorData { use router::connector::Worldpay; utils::construct_connector_data_old( - Box::new(&Worldpay), + Box::new(Worldpay::new()), types::Connector::Worldpay, types::api::GetToken::Connector, None, diff --git a/crates/router/tests/connectors/xendit.rs b/crates/router/tests/connectors/xendit.rs new file mode 100644 index 000000000000..452f22a1122e --- /dev/null +++ b/crates/router/tests/connectors/xendit.rs @@ -0,0 +1,402 @@ +use masking::Secret; +use router::{ + types::{self, api, storage::enums, +}}; + +use crate::utils::{self, ConnectorActions}; +use test_utils::connector_auth; + +#[derive(Clone, Copy)] +struct XenditTest; +impl ConnectorActions for XenditTest {} +impl utils::Connector for XenditTest { + fn get_data(&self) -> api::ConnectorData { + use router::connector::Xendit; + api::ConnectorData { + connector: Box::new(Xendit::new()), + connector_name: types::Connector::Xendit, + get_token: types::api::GetToken::Connector, + merchant_connector_id: None, + } + } + + fn get_auth_token(&self) -> types::ConnectorAuthType { + utils::to_connector_auth_type( + connector_auth::ConnectorAuthentication::new() + .xendit + .expect("Missing connector authentication configuration").into(), + ) + } + + fn get_name(&self) -> String { + "xendit".to_string() + } +} + +static CONNECTOR: XenditTest = XenditTest {}; + +fn get_default_payment_info() -> Option { + None +} + +fn payment_method_details() -> Option { + None +} + +// Cards Positive Tests +// Creates a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_only_authorize_payment() { + let response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized); +} + +// Captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info()) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Partially captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment( + payment_method_details(), + Some(types::PaymentsCaptureData { + amount_to_capture: 50, + ..utils::PaymentCaptureType::default().0 + }), + get_default_payment_info(), + ) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_authorized_payment() { + let authorize_response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("PSync response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized,); +} + +// Voids a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_void_authorized_payment() { + let response = CONNECTOR + .authorize_and_void_payment( + payment_method_details(), + Some(types::PaymentsCancelData { + connector_transaction_id: String::from(""), + cancellation_reason: Some("requested_by_customer".to_string()), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("Void payment response"); + assert_eq!(response.status, enums::AttemptStatus::Voided); +} + +// Refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund(payment_method_details(), None, None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Synchronizes a refund using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_manually_captured_refund() { + let refund_response = CONNECTOR + .capture_payment_and_refund(payment_method_details(), None, None, get_default_payment_info()) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_make_payment() { + let authorize_response = CONNECTOR.make_payment(payment_method_details(), get_default_payment_info()).await.unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_auto_captured_payment() { + let authorize_response = CONNECTOR.make_payment(payment_method_details(), get_default_payment_info()).await.unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Charged, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + capture_method: Some(enums::CaptureMethod::Automatic), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!(response.status, enums::AttemptStatus::Charged,); +} + +// Refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_auto_captured_payment() { + let response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_succeeded_payment() { + let refund_response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + refund_response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_succeeded_payment_multiple_times() { + CONNECTOR + .make_payment_and_multiple_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await; +} + +// Synchronizes a refund using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_refund() { + let refund_response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Cards Negative scenarios +// Creates a payment with incorrect CVC. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_cvc() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: types::api::PaymentMethodData::Card(api::Card { + card_cvc: Secret::new("12345".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's security code is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry month. +#[actix_web::test] +async fn should_fail_payment_for_invalid_exp_month() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: api::PaymentMethodData::Card(api::Card { + card_exp_month: Secret::new("20".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration month is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry year. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_expiry_year() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: api::PaymentMethodData::Card(api::Card { + card_exp_year: Secret::new("2000".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration year is invalid.".to_string(), + ); +} + +// Voids a payment using automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_fail_void_payment_for_auto_capture() { + let authorize_response = CONNECTOR.make_payment(payment_method_details(), get_default_payment_info()).await.unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let void_response = CONNECTOR + .void_payment(txn_id.unwrap(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + void_response.response.unwrap_err().message, + "You cannot cancel this PaymentIntent because it has a status of succeeded." + ); +} + +// Captures a payment using invalid connector payment id. +#[actix_web::test] +async fn should_fail_capture_for_invalid_payment() { + let capture_response = CONNECTOR + .capture_payment("123456789".to_string(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + capture_response.response.unwrap_err().message, + String::from("No such payment_intent: '123456789'") + ); +} + +// Refunds a payment with refund amount higher than payment amount. +#[actix_web::test] +async fn should_fail_for_refund_amount_higher_than_payment_amount() { + let response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 150, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Refund amount (₹1.50) is greater than charge amount (₹1.00)", + ); +} + +// Connector dependent test cases goes here + +// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests diff --git a/crates/router/tests/connectors/zen.rs b/crates/router/tests/connectors/zen.rs index 20948a90c6d3..a13fe48d2559 100644 --- a/crates/router/tests/connectors/zen.rs +++ b/crates/router/tests/connectors/zen.rs @@ -1,8 +1,8 @@ use std::str::FromStr; -use api_models::payments::OrderDetailsWithAmount; use cards::CardNumber; use common_utils::{pii::Email, types::MinorUnit}; +use hyperswitch_domain_models::types::OrderDetailsWithAmount; use masking::Secret; use router::types::{self, domain, storage::enums}; @@ -325,7 +325,7 @@ async fn should_fail_payment_for_incorrect_card_number() { order_details: Some(vec![OrderDetailsWithAmount { product_name: "test".to_string(), quantity: 1, - amount: 1000, + amount: MinorUnit::new(1000), product_img_link: None, requires_shipping: None, product_id: None, @@ -334,6 +334,8 @@ async fn should_fail_payment_for_incorrect_card_number() { brand: None, product_type: None, product_tax_code: None, + tax_rate: None, + total_tax_amount: None, }]), email: Some(Email::from_str("test@gmail.com").unwrap()), webhook_url: Some("https://1635-116-74-253-164.ngrok-free.app".to_string()), @@ -368,7 +370,7 @@ async fn should_fail_payment_for_incorrect_cvc() { order_details: Some(vec![OrderDetailsWithAmount { product_name: "test".to_string(), quantity: 1, - amount: 1000, + amount: MinorUnit::new(1000), product_img_link: None, requires_shipping: None, product_id: None, @@ -377,6 +379,8 @@ async fn should_fail_payment_for_incorrect_cvc() { brand: None, product_type: None, product_tax_code: None, + tax_rate: None, + total_tax_amount: None, }]), email: Some(Email::from_str("test@gmail.com").unwrap()), webhook_url: Some("https://1635-116-74-253-164.ngrok-free.app".to_string()), @@ -411,7 +415,7 @@ async fn should_fail_payment_for_invalid_exp_month() { order_details: Some(vec![OrderDetailsWithAmount { product_name: "test".to_string(), quantity: 1, - amount: 1000, + amount: MinorUnit::new(1000), product_img_link: None, requires_shipping: None, product_id: None, @@ -420,6 +424,8 @@ async fn should_fail_payment_for_invalid_exp_month() { brand: None, product_type: None, product_tax_code: None, + tax_rate: None, + total_tax_amount: None, }]), email: Some(Email::from_str("test@gmail.com").unwrap()), webhook_url: Some("https://1635-116-74-253-164.ngrok-free.app".to_string()), @@ -454,7 +460,7 @@ async fn should_fail_payment_for_incorrect_expiry_year() { order_details: Some(vec![OrderDetailsWithAmount { product_name: "test".to_string(), quantity: 1, - amount: 1000, + amount: MinorUnit::new(1000), product_img_link: None, requires_shipping: None, product_id: None, @@ -463,6 +469,8 @@ async fn should_fail_payment_for_incorrect_expiry_year() { brand: None, product_type: None, product_tax_code: None, + tax_rate: None, + total_tax_amount: None, }]), email: Some(Email::from_str("test@gmail.com").unwrap()), webhook_url: Some("https://1635-116-74-253-164.ngrok-free.app".to_string()), diff --git a/crates/router/tests/payments.rs b/crates/router/tests/payments.rs index 435e39acae12..beaacb79fc01 100644 --- a/crates/router/tests/payments.rs +++ b/crates/router/tests/payments.rs @@ -295,7 +295,10 @@ async fn payments_create_core() { let merchant_id = id_type::MerchantId::try_from(Cow::from("juspay_merchant")).unwrap(); let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let key_manager_state = &(&state).into(); let key_store = state @@ -441,11 +444,12 @@ async fn payments_create_core() { payment_method_id: None, payment_method_status: None, updated: None, - charges: None, + split_payments: None, frm_metadata: None, merchant_order_reference_id: None, order_tax_amount: None, connector_mandate_id: None, + shipping_cost: None, }; let expected_response = services::ApplicationResponse::JsonWithHeaders((expected_response, vec![])); @@ -467,7 +471,8 @@ async fn payments_create_core() { services::AuthFlow::Merchant, payments::CallConnectorAction::Trigger, None, - api::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), + None, )) .await .unwrap(); @@ -551,7 +556,10 @@ async fn payments_create_core_adyen_no_redirect() { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let payment_id = @@ -699,11 +707,12 @@ async fn payments_create_core_adyen_no_redirect() { payment_method_id: None, payment_method_status: None, updated: None, - charges: None, + split_payments: None, frm_metadata: None, merchant_order_reference_id: None, order_tax_amount: None, connector_mandate_id: None, + shipping_cost: None, }, vec![], )); @@ -725,7 +734,8 @@ async fn payments_create_core_adyen_no_redirect() { services::AuthFlow::Merchant, payments::CallConnectorAction::Trigger, None, - api::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), + None, )) .await .unwrap(); diff --git a/crates/router/tests/payments2.rs b/crates/router/tests/payments2.rs index b6c22c469d44..1d573d007ba6 100644 --- a/crates/router/tests/payments2.rs +++ b/crates/router/tests/payments2.rs @@ -56,7 +56,10 @@ async fn payments_create_core() { let merchant_id = id_type::MerchantId::try_from(Cow::from("juspay_merchant")).unwrap(); let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let key_manager_state = &(&state).into(); let key_store = state @@ -202,11 +205,12 @@ async fn payments_create_core() { payment_method_id: None, payment_method_status: None, updated: None, - charges: None, + split_payments: None, frm_metadata: None, merchant_order_reference_id: None, order_tax_amount: None, connector_mandate_id: None, + shipping_cost: None, }; let expected_response = @@ -229,7 +233,8 @@ async fn payments_create_core() { services::AuthFlow::Merchant, payments::CallConnectorAction::Trigger, None, - api::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), + None, )) .await .unwrap(); @@ -320,7 +325,10 @@ async fn payments_create_core_adyen_no_redirect() { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let customer_id = format!("cust_{}", Uuid::new_v4()); @@ -469,11 +477,12 @@ async fn payments_create_core_adyen_no_redirect() { payment_method_id: None, payment_method_status: None, updated: None, - charges: None, + split_payments: None, frm_metadata: None, merchant_order_reference_id: None, order_tax_amount: None, connector_mandate_id: None, + shipping_cost: None, }, vec![], )); @@ -495,7 +504,8 @@ async fn payments_create_core_adyen_no_redirect() { services::AuthFlow::Merchant, payments::CallConnectorAction::Trigger, None, - api::HeaderPayload::default(), + hyperswitch_domain_models::payments::HeaderPayload::default(), + None, )) .await .unwrap(); diff --git a/crates/router/tests/services.rs b/crates/router/tests/services.rs index d907000cccd9..c014370b24f4 100644 --- a/crates/router/tests/services.rs +++ b/crates/router/tests/services.rs @@ -18,7 +18,10 @@ async fn get_redis_conn_failure() { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); let _ = state.store.get_redis_conn().map(|conn| { @@ -46,7 +49,10 @@ async fn get_redis_conn_success() { )) .await; let state = Arc::new(app_state) - .get_session_state("public", || {}) + .get_session_state( + &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + || {}, + ) .unwrap(); // Act diff --git a/crates/router_derive/src/lib.rs b/crates/router_derive/src/lib.rs index 02179934e384..d1fb543318f2 100644 --- a/crates/router_derive/src/lib.rs +++ b/crates/router_derive/src/lib.rs @@ -166,7 +166,6 @@ pub fn diesel_enum( /// } /// ``` /// - /// # Panics /// /// Panics if a struct without named fields is provided as input to the macro @@ -505,7 +504,6 @@ pub fn operation_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStre /// payment_method: String, /// } /// ``` - #[proc_macro_derive( PolymorphicSchema, attributes(mandatory_in, generate_schemas, remove_in) @@ -620,6 +618,7 @@ pub fn try_get_enum_variant(input: proc_macro::TokenStream) -> proc_macro::Token /// /// Example /// +/// ``` /// #[derive(Default, Serialize, FlatStruct)] /// pub struct User { /// name: String, @@ -644,6 +643,7 @@ pub fn try_get_enum_variant(input: proc_macro::TokenStream) -> proc_macro::Token /// ("address.zip", "941222"), /// ("email", "test@example.com"), /// ] +/// ``` #[proc_macro_derive(FlatStruct)] pub fn flat_struct_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as syn::DeriveInput); @@ -694,3 +694,73 @@ pub fn flat_struct_derive(input: proc_macro::TokenStream) -> proc_macro::TokenSt proc_macro::TokenStream::from(expanded) } + +/// Generates the permissions enum and implematations for the permissions +/// +/// **NOTE:** You have to make sure that all the identifiers used +/// in the macro input are present in the respective enums as well. +/// +/// ## Usage +/// ``` +/// use router_derive::generate_permissions; +/// +/// enum Scope { +/// Read, +/// Write, +/// } +/// +/// enum EntityType { +/// Profile, +/// Merchant, +/// Org, +/// } +/// +/// enum Resource { +/// Payments, +/// Refunds, +/// } +/// +/// generate_permissions! { +/// permissions: [ +/// Payments: { +/// scopes: [Read, Write], +/// entities: [Profile, Merchant, Org] +/// }, +/// Refunds: { +/// scopes: [Read], +/// entities: [Profile, Org] +/// } +/// ] +/// } +/// ``` +/// This will generate the following enum. +/// ``` +/// enum Permission { +/// ProfilePaymentsRead, +/// ProfilePaymentsWrite, +/// MerchantPaymentsRead, +/// MerchantPaymentsWrite, +/// OrgPaymentsRead, +/// OrgPaymentsWrite, +/// ProfileRefundsRead, +/// OrgRefundsRead, +/// ``` +#[proc_macro] +pub fn generate_permissions(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + macros::generate_permissions_inner(input) +} + +/// Generates the ToEncryptable trait for a type +/// +/// This macro generates the temporary structs which has the fields that needs to be encrypted +/// +/// fn to_encryptable: Convert the temp struct to a hashmap that can be sent over the network +/// fn from_encryptable: Convert the hashmap back to temp struct +#[proc_macro_derive(ToEncryption, attributes(encrypt))] +pub fn derive_to_encryption_attr(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = syn::parse_macro_input!(input as syn::DeriveInput); + + macros::derive_to_encryption(input) + .unwrap_or_else(|err| err.into_compile_error()) + .into() +} diff --git a/crates/router_derive/src/macros.rs b/crates/router_derive/src/macros.rs index 9a8e514c5c11..e227c6533e9b 100644 --- a/crates/router_derive/src/macros.rs +++ b/crates/router_derive/src/macros.rs @@ -1,8 +1,10 @@ pub(crate) mod api_error; pub(crate) mod diesel; +pub(crate) mod generate_permissions; pub(crate) mod generate_schema; pub(crate) mod misc; pub(crate) mod operation; +pub(crate) mod to_encryptable; pub(crate) mod try_get_enum; mod helpers; @@ -14,7 +16,9 @@ use syn::DeriveInput; pub(crate) use self::{ api_error::api_error_derive_inner, diesel::{diesel_enum_derive_inner, diesel_enum_text_derive_inner}, + generate_permissions::generate_permissions_inner, generate_schema::polymorphic_macro_derive_inner, + to_encryptable::derive_to_encryption, }; pub(crate) fn debug_as_display_inner(ast: &DeriveInput) -> syn::Result { diff --git a/crates/router_derive/src/macros/generate_permissions.rs b/crates/router_derive/src/macros/generate_permissions.rs new file mode 100644 index 000000000000..9b388f102cb6 --- /dev/null +++ b/crates/router_derive/src/macros/generate_permissions.rs @@ -0,0 +1,135 @@ +use proc_macro::TokenStream; +use quote::{format_ident, quote}; +use syn::{ + braced, bracketed, + parse::{Parse, ParseBuffer, ParseStream}, + parse_macro_input, + punctuated::Punctuated, + token::Comma, + Ident, Token, +}; + +struct ResourceInput { + resource_name: Ident, + scopes: Punctuated, + entities: Punctuated, +} + +struct Input { + permissions: Punctuated, +} + +impl Parse for Input { + fn parse(input: ParseStream<'_>) -> syn::Result { + let (_permission_label, permissions) = parse_label_with_punctuated_data(input)?; + + Ok(Self { permissions }) + } +} + +impl Parse for ResourceInput { + fn parse(input: ParseStream<'_>) -> syn::Result { + let resource_name: Ident = input.parse()?; + input.parse::()?; // Expect ':' + + let content; + braced!(content in input); + + let (_scopes_label, scopes) = parse_label_with_punctuated_data(&content)?; + content.parse::()?; + + let (_entities_label, entities) = parse_label_with_punctuated_data(&content)?; + + Ok(Self { + resource_name, + scopes, + entities, + }) + } +} + +fn parse_label_with_punctuated_data( + input: &ParseBuffer<'_>, +) -> syn::Result<(Ident, Punctuated)> { + let label: Ident = input.parse()?; + input.parse::()?; // Expect ':' + + let content; + bracketed!(content in input); // Parse the list inside [] + let data = Punctuated::::parse_terminated(&content)?; + + Ok((label, data)) +} + +pub fn generate_permissions_inner(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as Input); + + let res = input.permissions.iter(); + + let mut enum_keys = Vec::new(); + let mut scope_impl_per = Vec::new(); + let mut entity_impl_per = Vec::new(); + let mut resource_impl_per = Vec::new(); + + let mut entity_impl_res = Vec::new(); + + for per in res { + let resource_name = &per.resource_name; + let mut permissions = Vec::new(); + + for scope in per.scopes.iter() { + for entity in per.entities.iter() { + let key = format_ident!("{}{}{}", entity, per.resource_name, scope); + + enum_keys.push(quote! { #key }); + scope_impl_per.push(quote! { Permission::#key => PermissionScope::#scope }); + entity_impl_per.push(quote! { Permission::#key => EntityType::#entity }); + resource_impl_per.push(quote! { Permission::#key => Resource::#resource_name }); + permissions.push(quote! { Permission::#key }); + } + let entities_iter = per.entities.iter(); + entity_impl_res + .push(quote! { Resource::#resource_name => vec![#(EntityType::#entities_iter),*] }); + } + } + + let expanded = quote! { + #[derive( + Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, serde::Serialize, serde::Deserialize, strum::Display + )] + pub enum Permission { + #(#enum_keys),* + } + + impl Permission { + pub fn scope(&self) -> PermissionScope { + match self { + #(#scope_impl_per),* + } + } + pub fn entity_type(&self) -> EntityType { + match self { + #(#entity_impl_per),* + } + } + pub fn resource(&self) -> Resource { + match self { + #(#resource_impl_per),* + } + } + } + + pub trait ResourceExt { + fn entities(&self) -> Vec; + } + + impl ResourceExt for Resource { + fn entities(&self) -> Vec { + match self { + #(#entity_impl_res),* + } + } + } + }; + expanded.into() +} diff --git a/crates/router_derive/src/macros/operation.rs b/crates/router_derive/src/macros/operation.rs index 638e6a506f25..995db7a6cece 100644 --- a/crates/router_derive/src/macros/operation.rs +++ b/crates/router_derive/src/macros/operation.rs @@ -5,7 +5,7 @@ use quote::{quote, ToTokens}; use strum::IntoEnumIterator; use syn::{self, parse::Parse, DeriveInput}; -use crate::macros::helpers::{self}; +use crate::macros::helpers; #[derive(Debug, Clone, Copy, strum::EnumString, strum::EnumIter, strum::Display)] #[strum(serialize_all = "snake_case")] @@ -31,6 +31,8 @@ pub enum Derives { IncrementalAuthorizationData, SdkSessionUpdate, SdkSessionUpdateData, + PostSessionTokens, + PostSessionTokensData, } impl Derives { @@ -42,7 +44,7 @@ impl Derives { let req_type = Conversion::get_req_type(self); quote! { #[automatically_derived] - impl Operation for #struct_name { + impl Operation for #struct_name { type Data = PaymentData; #(#fns)* } @@ -57,7 +59,7 @@ impl Derives { let req_type = Conversion::get_req_type(self); quote! { #[automatically_derived] - impl Operation for &#struct_name { + impl Operation for &#struct_name { type Data = PaymentData; #(#ref_fns)* } @@ -113,6 +115,12 @@ impl Conversion { Derives::SdkSessionUpdateData => { syn::Ident::new("SdkPaymentsSessionUpdateData", Span::call_site()) } + Derives::PostSessionTokens => { + syn::Ident::new("PaymentsPostSessionTokensRequest", Span::call_site()) + } + Derives::PostSessionTokensData => { + syn::Ident::new("PaymentsPostSessionTokensData", Span::call_site()) + } } } @@ -434,6 +442,7 @@ pub fn operation_derive_inner(input: DeriveInput) -> syn::Result syn::Result) -> syn::Result { + let _meta_type: Ident = input.parse()?; + input.parse::()?; + let value: Ident = input.parse()?; + Ok(Self { _meta_type, value }) + } +} + +impl quote::ToTokens for FieldMeta { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + self.value.to_tokens(tokens); + } +} + +fn get_encryption_ty_meta(field: &Field) -> Option { + let attrs = &field.attrs; + + attrs + .iter() + .flat_map(|s| s.parse_args::()) + .find(|s| s._meta_type.eq("ty")) +} + +fn get_inner_type(path: &syn::TypePath) -> syn::Result { + path.path + .segments + .last() + .and_then(|segment| match &segment.arguments { + syn::PathArguments::AngleBracketed(args) => args.args.first(), + _ => None, + }) + .and_then(|arg| match arg { + syn::GenericArgument::Type(SynType::Path(path)) => Some(path.clone()), + _ => None, + }) + .ok_or_else(|| { + syn::Error::new( + proc_macro2::Span::call_site(), + "Only path fields are supported", + ) + }) +} + +/// This function returns the inner most type recursively +/// For example: +/// +/// In the case of `Encryptable>> this returns String +fn get_inner_most_type(ty: SynType) -> syn::Result { + fn get_inner_type_recursive(path: syn::TypePath) -> syn::Result { + match get_inner_type(&path) { + Ok(inner_path) => get_inner_type_recursive(inner_path), + Err(_) => Ok(path), + } + } + + match ty { + SynType::Path(path) => { + let inner_path = get_inner_type_recursive(path)?; + inner_path + .path + .segments + .last() + .map(|last_segment| last_segment.ident.to_owned()) + .ok_or_else(|| { + syn::Error::new( + proc_macro2::Span::call_site(), + "At least one ident must be specified", + ) + }) + } + _ => Err(syn::Error::new( + proc_macro2::Span::call_site(), + "Only path fields are supported", + )), + } +} + +/// This returns the field which implement #[encrypt] attribute +fn get_encryptable_fields(fields: Punctuated) -> Vec { + fields + .into_iter() + .filter(|field| { + field + .attrs + .iter() + .any(|attr| attr.path().is_ident("encrypt")) + }) + .collect() +} + +/// This function returns the inner most type of a field +fn get_field_and_inner_types(fields: &[Field]) -> Vec<(Field, Ident)> { + fields + .iter() + .flat_map(|field| { + get_inner_most_type(field.ty.clone()).map(|field_name| (field.to_owned(), field_name)) + }) + .collect() +} + +/// The type of the struct for which the batch encryption/decryption needs to be implemented +#[derive(PartialEq, Copy, Clone)] +enum StructType { + Encrypted, + Decrypted, + DecryptedUpdate, + FromRequest, + Updated, +} + +impl StructType { + /// Generates the fields for temporary structs which consists of the fields that should be + /// encrypted/decrypted + fn generate_struct_fields(self, fields: &[(Field, Ident)]) -> Vec { + fields + .iter() + .map(|(field, inner_ty)| { + let provided_ty = get_encryption_ty_meta(field); + let is_option = get_field_type(field.ty.clone()) + .map(|f| f.eq("Option")) + .unwrap_or_default(); + let ident = &field.ident; + let inner_ty = if let Some(ref ty) = provided_ty { + &ty.value + } else { + inner_ty + }; + match (self, is_option) { + (Self::Encrypted, true) => quote! { pub #ident: Option }, + (Self::Encrypted, false) => quote! { pub #ident: Encryption }, + (Self::Decrypted, true) => { + quote! { pub #ident: Option>> } + } + (Self::Decrypted, false) => { + quote! { pub #ident: Encryptable> } + } + (Self::DecryptedUpdate, _) => { + quote! { pub #ident: Option>> } + } + (Self::FromRequest, true) => { + quote! { pub #ident: Option> } + } + (Self::FromRequest, false) => quote! { pub #ident: Secret<#inner_ty> }, + (Self::Updated, _) => quote! { pub #ident: Option> }, + } + }) + .collect() + } + + /// Generates the ToEncryptable trait implementation + fn generate_impls( + self, + gen1: proc_macro2::TokenStream, + gen2: proc_macro2::TokenStream, + gen3: proc_macro2::TokenStream, + impl_st: proc_macro2::TokenStream, + inner: &[Field], + ) -> proc_macro2::TokenStream { + let map_length = inner.len(); + + let to_encryptable_impl = inner.iter().flat_map(|field| { + get_field_type(field.ty.clone()).map(|field_ty| { + let is_option = field_ty.eq("Option"); + let field_ident = &field.ident; + let field_ident_string = field_ident.as_ref().map(|s| s.to_string()); + + if is_option || self == Self::Updated { + quote! { self.#field_ident.map(|s| map.insert(#field_ident_string.to_string(), s)) } + } else { + quote! { map.insert(#field_ident_string.to_string(), self.#field_ident) } + } + }) + }); + + let from_encryptable_impl = inner.iter().flat_map(|field| { + get_field_type(field.ty.clone()).map(|field_ty| { + let is_option = field_ty.eq("Option"); + let field_ident = &field.ident; + let field_ident_string = field_ident.as_ref().map(|s| s.to_string()); + + if is_option || self == Self::Updated { + quote! { #field_ident: map.remove(#field_ident_string) } + } else { + quote! { + #field_ident: map.remove(#field_ident_string).ok_or( + error_stack::report!(common_utils::errors::ParsingError::EncodeError( + "Unable to convert from HashMap", + )) + )? + } + } + }) + }); + + quote! { + impl ToEncryptable<#gen1, #gen2, #gen3> for #impl_st { + fn to_encryptable(self) -> FxHashMap { + let mut map = FxHashMap::with_capacity_and_hasher(#map_length, Default::default()); + #(#to_encryptable_impl;)* + map + } + + fn from_encryptable( + mut map: FxHashMap>, + ) -> CustomResult<#gen1, common_utils::errors::ParsingError> { + Ok(#gen1 { + #(#from_encryptable_impl,)* + }) + } + } + } + } +} + +/// This function generates the temporary struct and ToEncryptable impls for the temporary structs +fn generate_to_encryptable( + struct_name: Ident, + fields: Vec, +) -> syn::Result { + let struct_types = [ + // The first two are to be used as return types we do not need to implement ToEncryptable + // on it + ("Decrypted", StructType::Decrypted), + ("DecryptedUpdate", StructType::DecryptedUpdate), + ("FromRequestEncryptable", StructType::FromRequest), + ("Encrypted", StructType::Encrypted), + ("UpdateEncryptable", StructType::Updated), + ]; + + let inner_types = get_field_and_inner_types(&fields); + + let inner_type = inner_types.first().ok_or_else(|| { + syn::Error::new( + proc_macro2::Span::call_site(), + "Please use the macro with attribute #[encrypt] on the fields you want to encrypt", + ) + })?; + + let provided_ty = get_encryption_ty_meta(&inner_type.0) + .map(|ty| ty.value.clone()) + .unwrap_or(inner_type.1.clone()); + + let structs = struct_types.iter().map(|(prefix, struct_type)| { + let name = format_ident!("{}{}", prefix, struct_name); + let temp_fields = struct_type.generate_struct_fields(&inner_types); + quote! { + pub struct #name { + #(#temp_fields,)* + } + } + }); + + // These implementations shouldn't be implemented Decrypted and DecryptedUpdate temp structs + // So skip the first two entries in the list + let impls = struct_types + .iter() + .skip(2) + .map(|(prefix, struct_type)| { + let name = format_ident!("{}{}", prefix, struct_name); + + let impl_block = if *struct_type != StructType::DecryptedUpdate + || *struct_type != StructType::Decrypted + { + let (gen1, gen2, gen3) = match struct_type { + StructType::FromRequest => { + let decrypted_name = format_ident!("Decrypted{}", struct_name); + ( + quote! { #decrypted_name }, + quote! { Secret<#provided_ty> }, + quote! { Secret<#provided_ty> }, + ) + } + StructType::Encrypted => { + let decrypted_name = format_ident!("Decrypted{}", struct_name); + ( + quote! { #decrypted_name }, + quote! { Secret<#provided_ty> }, + quote! { Encryption }, + ) + } + StructType::Updated => { + let decrypted_update_name = format_ident!("DecryptedUpdate{}", struct_name); + ( + quote! { #decrypted_update_name }, + quote! { Secret<#provided_ty> }, + quote! { Secret<#provided_ty> }, + ) + } + //Unreachable statement + _ => (quote! {}, quote! {}, quote! {}), + }; + + struct_type.generate_impls(gen1, gen2, gen3, quote! { #name }, &fields) + } else { + quote! {} + }; + + Ok(quote! { + #impl_block + }) + }) + .collect::>>()?; + + Ok(quote! { + #(#structs)* + #(#impls)* + }) +} + +pub fn derive_to_encryption( + input: syn::DeriveInput, +) -> Result { + let struct_name = input.ident; + let fields = get_encryptable_fields(get_struct_fields(input.data)?); + + generate_to_encryptable(struct_name, fields) +} diff --git a/crates/router_env/Cargo.toml b/crates/router_env/Cargo.toml index 27fe451b753f..8a3482a0ac53 100644 --- a/crates/router_env/Cargo.toml +++ b/crates/router_env/Cargo.toml @@ -13,8 +13,10 @@ config = { version = "0.14.0", features = ["toml"] } error-stack = "0.4.1" gethostname = "0.4.3" once_cell = "1.19.0" -opentelemetry = { version = "0.19.0", features = ["rt-tokio-current-thread", "metrics"] } -opentelemetry-otlp = { version = "0.12.0", features = ["metrics"] } +opentelemetry = { version = "0.27.1", default-features = false, features = ["internal-logs", "metrics", "trace"] } +opentelemetry-aws = { version = "0.15.0", default-features = false, features = ["internal-logs", "trace"] } +opentelemetry-otlp = { version = "0.27.0", default-features = false, features = ["grpc-tonic", "metrics", "trace"] } +opentelemetry_sdk = { version = "0.27.1", default-features = false, features = ["rt-tokio-current-thread", "metrics", "trace"] } rustc-hash = "1.1" serde = { version = "1.0.197", features = ["derive"] } serde_json = "1.0.115" @@ -23,10 +25,10 @@ strum = { version = "0.26.2", features = ["derive"] } time = { version = "0.3.35", default-features = false, features = ["formatting"] } tokio = { version = "1.37.0" } tracing = { workspace = true } -tracing-actix-web = { version = "0.7.10", features = ["opentelemetry_0_19", "uuid_v7"], optional = true } +tracing-actix-web = { version = "0.7.15", features = ["opentelemetry_0_27", "uuid_v7"], optional = true } tracing-appender = { version = "0.2.3" } tracing-attributes = "0.1.27" -tracing-opentelemetry = { version = "0.19.0" } +tracing-opentelemetry = { version = "0.28.0", default-features = false } tracing-subscriber = { version = "0.3.18", default-features = true, features = ["env-filter", "json", "registry"] } vergen = { version = "8.3.1", optional = true, features = ["cargo", "git", "git2", "rustc"] } diff --git a/crates/router_env/src/lib.rs b/crates/router_env/src/lib.rs index 0d46f8ac3325..9b3aec4c949b 100644 --- a/crates/router_env/src/lib.rs +++ b/crates/router_env/src/lib.rs @@ -1,8 +1,6 @@ #![warn(missing_debug_implementations)] -//! //! Environment of payment router: logger, basic config, its environment awareness. -//! #![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR" ), "/", "README.md"))] diff --git a/crates/router_env/src/logger/mod.rs b/crates/router_env/src/logger.rs similarity index 98% rename from crates/router_env/src/logger/mod.rs rename to crates/router_env/src/logger.rs index a7aa0610e9b4..2b0ae49b1f65 100644 --- a/crates/router_env/src/logger/mod.rs +++ b/crates/router_env/src/logger.rs @@ -1,6 +1,4 @@ -//! //! Logger of the system. -//! pub use tracing::{debug, error, event as log, info, warn}; pub use tracing_attributes::instrument; diff --git a/crates/router_env/src/logger/config.rs b/crates/router_env/src/logger/config.rs index 03cc9ac17bc8..431fb5a6368e 100644 --- a/crates/router_env/src/logger/config.rs +++ b/crates/router_env/src/logger/config.rs @@ -1,6 +1,4 @@ -//! //! Logger-specific config. -//! use std::path::PathBuf; @@ -49,7 +47,7 @@ pub struct Level(pub(super) tracing::Level); impl Level { /// Returns the most verbose [`tracing::Level`] - pub fn into_level(&self) -> tracing::Level { + pub fn into_level(self) -> tracing::Level { self.0 } } diff --git a/crates/router_env/src/logger/formatter.rs b/crates/router_env/src/logger/formatter.rs index 6bcf669e73a7..bbcf1e230616 100644 --- a/crates/router_env/src/logger/formatter.rs +++ b/crates/router_env/src/logger/formatter.rs @@ -1,6 +1,4 @@ -//! //! Formatting [layer](https://docs.rs/tracing-subscriber/0.3.15/tracing_subscriber/layer/trait.Layer.html) for Router. -//! use std::{ collections::{HashMap, HashSet}, @@ -117,10 +115,8 @@ impl fmt::Display for RecordType { } } -/// /// Format log records. /// `FormattingLayer` relies on the `tracing_bunyan_formatter::JsonStorageLayer` which is storage of entries. -/// #[derive(Debug)] pub struct FormattingLayer where @@ -145,7 +141,6 @@ where W: for<'a> MakeWriter<'a> + 'static, F: Formatter + Clone, { - /// /// Constructor of `FormattingLayer`. /// /// A `name` will be attached to all records during formatting. @@ -155,7 +150,6 @@ where /// ```rust /// let formatting_layer = router_env::FormattingLayer::new("my_service", std::io::stdout, serde_json::ser::CompactFormatter); /// ``` - /// pub fn new( service: &str, dst_writer: W, @@ -296,11 +290,9 @@ where Ok(()) } - /// /// Flush memory buffer into an output stream trailing it with next line. /// /// Should be done by single `write_all` call to avoid fragmentation of log because of mutlithreading. - /// fn flush(&self, mut buffer: Vec) -> Result<(), std::io::Error> { buffer.write_all(b"\n")?; self.dst_writer.make_writer().write_all(&buffer) @@ -338,7 +330,7 @@ where /// Serialize event into a buffer of bytes using parent span. pub fn event_serialize( &self, - span: &Option<&SpanRef<'_, S>>, + span: Option<&SpanRef<'_, S>>, event: &Event<'_>, ) -> std::io::Result> where @@ -355,18 +347,15 @@ where let name = span.map_or("?", SpanRef::name); Self::event_message(span, event, &mut storage); - self.common_serialize(&mut map_serializer, event.metadata(), *span, &storage, name)?; + self.common_serialize(&mut map_serializer, event.metadata(), span, &storage, name)?; map_serializer.end()?; Ok(buffer) } - /// /// Format message of a span. /// /// Example: "[FN_WITHOUT_COLON - START]" - /// - fn span_message(span: &SpanRef<'_, S>, ty: RecordType) -> String where S: Subscriber + for<'a> LookupSpan<'a>, @@ -374,17 +363,11 @@ where format!("[{} - {}]", span.metadata().name().to_uppercase(), ty) } - /// /// Format message of an event. /// /// Examples: "[FN_WITHOUT_COLON - EVENT] Message" - /// - - fn event_message( - span: &Option<&SpanRef<'_, S>>, - event: &Event<'_>, - storage: &mut Storage<'_>, - ) where + fn event_message(span: Option<&SpanRef<'_, S>>, event: &Event<'_>, storage: &mut Storage<'_>) + where S: Subscriber + for<'a> LookupSpan<'a>, { // Get value of kept "message" or "target" if does not exist. @@ -411,7 +394,7 @@ where // Event could have no span. let span = ctx.lookup_current(); - let result: std::io::Result> = self.event_serialize(&span.as_ref(), event); + let result: std::io::Result> = self.event_serialize(span.as_ref(), event); if let Ok(formatted) = result { let _ = self.flush(formatted); } diff --git a/crates/router_env/src/logger/setup.rs b/crates/router_env/src/logger/setup.rs index 428112fb37c3..7447c8787e06 100644 --- a/crates/router_env/src/logger/setup.rs +++ b/crates/router_env/src/logger/setup.rs @@ -3,20 +3,6 @@ use std::time::Duration; use ::config::ConfigError; -use opentelemetry::{ - global, runtime, - sdk::{ - export::metrics::aggregation::cumulative_temporality_selector, - metrics::{controllers::BasicController, selectors::simple}, - propagation::TraceContextPropagator, - trace, - trace::BatchConfig, - Resource, - }, - trace::{TraceContextExt, TraceState}, - KeyValue, -}; -use opentelemetry_otlp::{TonicExporterBuilder, WithExportConfig}; use serde_json::ser::{CompactFormatter, PrettyFormatter}; use tracing_appender::non_blocking::WorkerGuard; use tracing_subscriber::{fmt, prelude::*, util::SubscriberInitExt, EnvFilter, Layer}; @@ -27,7 +13,6 @@ use crate::{config, FormattingLayer, StorageSubscription}; #[derive(Debug)] pub struct TelemetryGuard { _log_guards: Vec, - _metrics_controller: Option, } /// Setup logging sub-system specifying the logging configuration, service (binary) name, and a @@ -47,10 +32,9 @@ pub fn setup( } else { None }; - let _metrics_controller = if config.telemetry.metrics_enabled { + + if config.telemetry.metrics_enabled { setup_metrics_pipeline(&config.telemetry) - } else { - None }; // Setup file logging @@ -132,21 +116,23 @@ pub fn setup( // dropped Ok(TelemetryGuard { _log_guards: guards, - _metrics_controller, }) } -fn get_opentelemetry_exporter(config: &config::LogTelemetry) -> TonicExporterBuilder { - let mut exporter_builder = opentelemetry_otlp::new_exporter().tonic(); +fn get_opentelemetry_exporter_config( + config: &config::LogTelemetry, +) -> opentelemetry_otlp::ExportConfig { + let mut exporter_config = opentelemetry_otlp::ExportConfig { + protocol: opentelemetry_otlp::Protocol::Grpc, + endpoint: config.otel_exporter_otlp_endpoint.clone(), + ..Default::default() + }; - if let Some(ref endpoint) = config.otel_exporter_otlp_endpoint { - exporter_builder = exporter_builder.with_endpoint(endpoint); - } if let Some(timeout) = config.otel_exporter_otlp_timeout { - exporter_builder = exporter_builder.with_timeout(Duration::from_millis(timeout)); + exporter_config.timeout = Duration::from_millis(timeout); } - exporter_builder + exporter_config } #[derive(Debug, Clone)] @@ -181,9 +167,7 @@ struct TraceAssertion { } impl TraceAssertion { - /// /// Should the provided url be traced - /// fn should_trace_url(&self, url: &str) -> bool { match &self.clauses { Some(clauses) => clauses.iter().all(|cur| cur.compare_url(url)), @@ -192,43 +176,43 @@ impl TraceAssertion { } } -/// /// Conditional Sampler for providing control on url based tracing -/// #[derive(Clone, Debug)] -struct ConditionalSampler(TraceAssertion, T); +struct ConditionalSampler( + TraceAssertion, + T, +); -impl trace::ShouldSample for ConditionalSampler { +impl + opentelemetry_sdk::trace::ShouldSample for ConditionalSampler +{ fn should_sample( &self, parent_context: Option<&opentelemetry::Context>, trace_id: opentelemetry::trace::TraceId, name: &str, span_kind: &opentelemetry::trace::SpanKind, - attributes: &opentelemetry::trace::OrderMap, + attributes: &[opentelemetry::KeyValue], links: &[opentelemetry::trace::Link], - instrumentation_library: &opentelemetry::InstrumentationLibrary, ) -> opentelemetry::trace::SamplingResult { + use opentelemetry::trace::TraceContextExt; + match attributes - .get(&opentelemetry::Key::new("http.route")) + .iter() + .find(|&kv| kv.key == opentelemetry::Key::new("http.route")) .map_or(self.0.default, |inner| { - self.0.should_trace_url(&inner.as_str()) + self.0.should_trace_url(&inner.value.as_str()) }) { - true => self.1.should_sample( - parent_context, - trace_id, - name, - span_kind, - attributes, - links, - instrumentation_library, - ), + true => { + self.1 + .should_sample(parent_context, trace_id, name, span_kind, attributes, links) + } false => opentelemetry::trace::SamplingResult { decision: opentelemetry::trace::SamplingDecision::Drop, attributes: Vec::new(), trace_state: match parent_context { Some(ctx) => ctx.span().span_context().trace_state().clone(), - None => TraceState::default(), + None => opentelemetry::trace::TraceState::default(), }, }, } @@ -238,95 +222,117 @@ impl trace::ShouldSample for Condition fn setup_tracing_pipeline( config: &config::LogTelemetry, service_name: &str, -) -> Option> -{ - global::set_text_map_propagator(TraceContextPropagator::new()); +) -> Option< + tracing_opentelemetry::OpenTelemetryLayer< + tracing_subscriber::Registry, + opentelemetry_sdk::trace::Tracer, + >, +> { + use opentelemetry::trace::TracerProvider; + use opentelemetry_otlp::WithExportConfig; + use opentelemetry_sdk::trace; + + opentelemetry::global::set_text_map_propagator( + opentelemetry_sdk::propagation::TraceContextPropagator::new(), + ); + + // Set the export interval to 1 second + let batch_config = trace::BatchConfigBuilder::default() + .with_scheduled_delay(Duration::from_millis(1000)) + .build(); + + let exporter_result = opentelemetry_otlp::SpanExporter::builder() + .with_tonic() + .with_export_config(get_opentelemetry_exporter_config(config)) + .build(); + + let exporter = if config.ignore_errors { + #[allow(clippy::print_stderr)] // The logger hasn't been initialized yet + exporter_result + .inspect_err(|error| eprintln!("Failed to build traces exporter: {error:?}")) + .ok()? + } else { + // Safety: This is conditional, there is an option to avoid this behavior at runtime. + #[allow(clippy::expect_used)] + exporter_result.expect("Failed to build traces exporter") + }; - let mut trace_config = trace::config() + let mut provider_builder = trace::TracerProvider::builder() + .with_span_processor( + trace::BatchSpanProcessor::builder( + exporter, + // The runtime would have to be updated if a different web framework is used + opentelemetry_sdk::runtime::TokioCurrentThread, + ) + .with_batch_config(batch_config) + .build(), + ) .with_sampler(trace::Sampler::ParentBased(Box::new(ConditionalSampler( TraceAssertion { clauses: config .route_to_trace .clone() - .map(|inner| inner.into_iter().map(Into::into).collect()), + .map(|inner| inner.into_iter().map(TraceUrlAssert::from).collect()), default: false, }, trace::Sampler::TraceIdRatioBased(config.sampling_rate.unwrap_or(1.0)), )))) - .with_resource(Resource::new(vec![KeyValue::new( - "service.name", - service_name.to_owned(), - )])); + .with_resource(opentelemetry_sdk::Resource::new(vec![ + opentelemetry::KeyValue::new("service.name", service_name.to_owned()), + ])); + if config.use_xray_generator { - trace_config = trace_config.with_id_generator(trace::XrayIdGenerator::default()); + provider_builder = provider_builder + .with_id_generator(opentelemetry_aws::trace::XrayIdGenerator::default()); } - // Change the default export interval from 5 seconds to 1 second - let batch_config = BatchConfig::default().with_scheduled_delay(Duration::from_millis(1000)); - - let traces_layer_result = opentelemetry_otlp::new_pipeline() - .tracing() - .with_exporter(get_opentelemetry_exporter(config)) - .with_batch_config(batch_config) - .with_trace_config(trace_config) - .install_batch(runtime::TokioCurrentThread) - .map(|tracer| tracing_opentelemetry::layer().with_tracer(tracer)); - - #[allow(clippy::print_stderr)] // The logger hasn't been initialized yet - if config.ignore_errors { - traces_layer_result - .map_err(|error| { - eprintln!("Failed to create an `opentelemetry_otlp` tracer: {error:?}") - }) - .ok() - } else { - // Safety: This is conditional, there is an option to avoid this behavior at runtime. - #[allow(clippy::expect_used)] - Some(traces_layer_result.expect("Failed to create an `opentelemetry_otlp` tracer")) - } + Some( + tracing_opentelemetry::layer() + .with_tracer(provider_builder.build().tracer(service_name.to_owned())), + ) } -fn setup_metrics_pipeline(config: &config::LogTelemetry) -> Option { - let histogram_buckets = { - let mut init = 0.01; - let mut buckets: [f64; 15] = [0.0; 15]; - - for bucket in &mut buckets { - init *= 2.0; - *bucket = init; - } - buckets - }; +fn setup_metrics_pipeline(config: &config::LogTelemetry) { + use opentelemetry_otlp::WithExportConfig; - let metrics_controller_result = opentelemetry_otlp::new_pipeline() - .metrics( - simple::histogram(histogram_buckets), - cumulative_temporality_selector(), - // This would have to be updated if a different web framework is used - runtime::TokioCurrentThread, - ) - .with_exporter(get_opentelemetry_exporter(config)) - .with_period(Duration::from_secs(3)) - .with_timeout(Duration::from_secs(10)) - .with_resource(Resource::new(vec![KeyValue::new( - "pod", - std::env::var("POD_NAME").map_or( - "hyperswitch-server-default".into(), - Into::::into, - ), - )])) + let exporter_result = opentelemetry_otlp::MetricExporter::builder() + .with_tonic() + .with_temporality(opentelemetry_sdk::metrics::Temporality::Cumulative) + .with_export_config(get_opentelemetry_exporter_config(config)) .build(); - #[allow(clippy::print_stderr)] // The logger hasn't been initialized yet - if config.ignore_errors { - metrics_controller_result - .map_err(|error| eprintln!("Failed to setup metrics pipeline: {error:?}")) - .ok() + let exporter = if config.ignore_errors { + #[allow(clippy::print_stderr)] // The logger hasn't been initialized yet + exporter_result + .inspect_err(|error| eprintln!("Failed to build metrics exporter: {error:?}")) + .ok(); + return; } else { // Safety: This is conditional, there is an option to avoid this behavior at runtime. #[allow(clippy::expect_used)] - Some(metrics_controller_result.expect("Failed to setup metrics pipeline")) - } + exporter_result.expect("Failed to build metrics exporter") + }; + + let reader = opentelemetry_sdk::metrics::PeriodicReader::builder( + exporter, + // The runtime would have to be updated if a different web framework is used + opentelemetry_sdk::runtime::TokioCurrentThread, + ) + .with_interval(Duration::from_secs(3)) + .with_timeout(Duration::from_secs(10)) + .build(); + + let provider = opentelemetry_sdk::metrics::SdkMeterProvider::builder() + .with_reader(reader) + .with_resource(opentelemetry_sdk::Resource::new([ + opentelemetry::KeyValue::new( + "pod", + std::env::var("POD_NAME").unwrap_or(String::from("hyperswitch-server-default")), + ), + ])) + .build(); + + opentelemetry::global::set_meter_provider(provider); } fn get_envfilter( diff --git a/crates/router_env/src/logger/storage.rs b/crates/router_env/src/logger/storage.rs index ce220680bb15..cdaf06ecf93a 100644 --- a/crates/router_env/src/logger/storage.rs +++ b/crates/router_env/src/logger/storage.rs @@ -1,6 +1,4 @@ -//! //! Storing [layer](https://docs.rs/tracing-subscriber/0.3.15/tracing_subscriber/layer/trait.Layer.html) for Router. -//! use std::{collections::HashMap, fmt, time::Instant}; diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index 603c5cc91c1c..10183f75d5bc 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -1,6 +1,4 @@ -//! //! Types. -//! use serde::Deserialize; use strum::{Display, EnumString}; @@ -9,12 +7,9 @@ pub use tracing::{ Level, Value, }; -/// /// Category and tag of log event. /// /// Don't hesitate to add your variant if it is missing here. -/// - #[derive(Debug, Default, Deserialize, Clone, Display, EnumString)] pub enum Tag { /// General. @@ -93,6 +88,8 @@ pub enum Flow { ConfigKeyCreate, /// ConfigKey fetch flow. ConfigKeyFetch, + /// Enable platform account flow. + EnablePlatformAccount, /// ConfigKey Update flow. ConfigKeyUpdate, /// ConfigKey Delete flow. @@ -169,6 +166,12 @@ pub enum Flow { PaymentsFilters, /// Payments aggregates flow PaymentsAggregate, + /// Payments Create Intent flow + PaymentsCreateIntent, + /// Payments Get Intent flow + PaymentsGetIntent, + /// Payments Update Intent flow + PaymentsUpdateIntent, #[cfg(feature = "payouts")] /// Payouts create flow PayoutsCreate, @@ -350,6 +353,8 @@ pub enum Flow { DecisionManagerRetrieveConfig, /// Manual payment fulfillment acknowledgement FrmFulfillment, + /// Get connectors feature matrix + FeatureMatrix, /// Change password flow ChangePassword, /// Signout flow @@ -362,10 +367,10 @@ pub enum Flow { VerifyPaymentConnector, /// Internal user signup InternalUserSignup, + /// Create tenant level user + TenantUserCreate, /// Switch org SwitchOrg, - /// Switch merchant - SwitchMerchant, /// Switch merchant v2 SwitchMerchantV2, /// Switch profile @@ -374,8 +379,8 @@ pub enum Flow { GetAuthorizationInfo, /// Get Roles info GetRolesInfo, - /// List roles - ListRoles, + /// Get Parent Group Info + GetParentGroupInfo, /// List roles v2 ListRolesV2, /// List invitable roles at entity level @@ -384,24 +389,26 @@ pub enum Flow { ListUpdatableRolesAtEntityLevel, /// Get role GetRole, + /// Get parent info for role + GetRoleV2, /// Get role from token GetRoleFromToken, + /// Get resources and groups for role from token + GetRoleFromTokenV2, /// Update user role UpdateUserRole, /// Create merchant account for user in a org UserMerchantAccountCreate, + /// Create Org in a given tenancy + UserOrgMerchantCreate, /// Generate Sample Data GenerateSampleData, /// Delete Sample Data DeleteSampleData, - /// List merchant accounts for user - UserMerchantAccountList, /// Get details of a user GetUserDetails, /// Get details of a user role in a merchant account GetUserRoleDetails, - /// List users for merchant account - ListUsersForMerchantAccount, /// PaymentMethodAuth Link token create PmAuthLinkTokenCreate, /// PaymentMethodAuth Exchange token create @@ -434,12 +441,8 @@ pub enum Flow { VerifyEmailRequest, /// Update user account details UpdateUserAccountDetails, - /// Accept user invitation using merchant_ids - AcceptInvitation, /// Accept user invitation using entities AcceptInvitationsV2, - /// Select merchant from invitations - MerchantSelect, /// Accept user invitation using entities before user login AcceptInvitationsPreAuth, /// Initiate external authentication for a payment @@ -490,6 +493,18 @@ pub enum Flow { ListUsersInLineage, /// List invitations for user ListInvitationsForUser, + /// Get theme using lineage + GetThemeUsingLineage, + /// Get theme using theme id + GetThemeUsingThemeId, + /// Upload file to theme storage + UploadFileToThemeStorage, + /// Create theme + CreateTheme, + /// Update theme + UpdateTheme, + /// Delete theme + DeleteTheme, /// List initial webhook delivery attempts WebhookEventInitialDeliveryAttemptList, /// List delivery attempts for a webhook event @@ -510,11 +525,21 @@ pub enum Flow { PaymentsManualUpdate, /// Dynamic Tax Calcultion SessionUpdateTaxCalculation, + /// Payments confirm intent + PaymentsConfirmIntent, + /// Payments post session tokens flow + PaymentsPostSessionTokens, + /// Payments start redirection flow + PaymentStartRedirection, + /// Volume split on the routing type + VolumeSplitOnRoutingType, + /// Relay flow + Relay, + /// Relay retrieve flow + RelayRetrieve, } -/// /// Trait for providing generic behaviour to flow metric -/// pub trait FlowMetric: ToString + std::fmt::Debug + Clone {} impl FlowMetric for Flow {} diff --git a/crates/router_env/src/metrics.rs b/crates/router_env/src/metrics.rs index 780c010579f7..9a7efff04948 100644 --- a/crates/router_env/src/metrics.rs +++ b/crates/router_env/src/metrics.rs @@ -1,16 +1,5 @@ //! Utilities to easily create opentelemetry contexts, meters and metrics. -/// Create a metrics [`Context`][Context] with the specified name. -/// -/// [Context]: opentelemetry::Context -#[macro_export] -macro_rules! metrics_context { - ($name:ident) => { - pub(crate) static $name: once_cell::sync::Lazy<$crate::opentelemetry::Context> = - once_cell::sync::Lazy::new($crate::opentelemetry::Context::current); - }; -} - /// Create a global [`Meter`][Meter] with the specified name and an optional description. /// /// [Meter]: opentelemetry::metrics::Meter @@ -20,14 +9,14 @@ macro_rules! global_meter { static $name: once_cell::sync::Lazy<$crate::opentelemetry::metrics::Meter> = once_cell::sync::Lazy::new(|| $crate::opentelemetry::global::meter(stringify!($name))); }; - ($name:ident, $description:literal) => { - static $name: once_cell::sync::Lazy<$crate::opentelemetry::metrics::Meter> = - once_cell::sync::Lazy::new(|| $crate::opentelemetry::global::meter($description)); + ($meter:ident, $name:literal) => { + static $meter: once_cell::sync::Lazy<$crate::opentelemetry::metrics::Meter> = + once_cell::sync::Lazy::new(|| $crate::opentelemetry::global::meter(stringify!($name))); }; } /// Create a [`Counter`][Counter] metric with the specified name and an optional description, -/// associated with the specified meter. Note that the meter must be to a valid [`Meter`][Meter]. +/// associated with the specified meter. Note that the meter must be a valid [`Meter`][Meter]. /// /// [Counter]: opentelemetry::metrics::Counter /// [Meter]: opentelemetry::metrics::Meter @@ -36,36 +25,54 @@ macro_rules! counter_metric { ($name:ident, $meter:ident) => { pub(crate) static $name: once_cell::sync::Lazy< $crate::opentelemetry::metrics::Counter, - > = once_cell::sync::Lazy::new(|| $meter.u64_counter(stringify!($name)).init()); + > = once_cell::sync::Lazy::new(|| $meter.u64_counter(stringify!($name)).build()); }; ($name:ident, $meter:ident, description:literal) => { + #[doc = $description] pub(crate) static $name: once_cell::sync::Lazy< $crate::opentelemetry::metrics::Counter, - > = once_cell::sync::Lazy::new(|| $meter.u64_counter($description).init()); + > = once_cell::sync::Lazy::new(|| { + $meter + .u64_counter(stringify!($name)) + .with_description($description) + .build() + }); }; } -/// Create a [`Histogram`][Histogram] metric with the specified name and an optional description, -/// associated with the specified meter. Note that the meter must be to a valid [`Meter`][Meter]. +/// Create a [`Histogram`][Histogram] f64 metric with the specified name and an optional description, +/// associated with the specified meter. Note that the meter must be a valid [`Meter`][Meter]. /// /// [Histogram]: opentelemetry::metrics::Histogram /// [Meter]: opentelemetry::metrics::Meter #[macro_export] -macro_rules! histogram_metric { +macro_rules! histogram_metric_f64 { ($name:ident, $meter:ident) => { pub(crate) static $name: once_cell::sync::Lazy< $crate::opentelemetry::metrics::Histogram, - > = once_cell::sync::Lazy::new(|| $meter.f64_histogram(stringify!($name)).init()); + > = once_cell::sync::Lazy::new(|| { + $meter + .f64_histogram(stringify!($name)) + .with_boundaries($crate::metrics::f64_histogram_buckets()) + .build() + }); }; ($name:ident, $meter:ident, $description:literal) => { + #[doc = $description] pub(crate) static $name: once_cell::sync::Lazy< $crate::opentelemetry::metrics::Histogram, - > = once_cell::sync::Lazy::new(|| $meter.f64_histogram($description).init()); + > = once_cell::sync::Lazy::new(|| { + $meter + .f64_histogram(stringify!($name)) + .with_description($description) + .with_boundaries($crate::metrics::f64_histogram_buckets()) + .build() + }); }; } /// Create a [`Histogram`][Histogram] u64 metric with the specified name and an optional description, -/// associated with the specified meter. Note that the meter must be to a valid [`Meter`][Meter]. +/// associated with the specified meter. Note that the meter must be a valid [`Meter`][Meter]. /// /// [Histogram]: opentelemetry::metrics::Histogram /// [Meter]: opentelemetry::metrics::Meter @@ -74,64 +81,72 @@ macro_rules! histogram_metric_u64 { ($name:ident, $meter:ident) => { pub(crate) static $name: once_cell::sync::Lazy< $crate::opentelemetry::metrics::Histogram, - > = once_cell::sync::Lazy::new(|| $meter.u64_histogram(stringify!($name)).init()); + > = once_cell::sync::Lazy::new(|| { + $meter + .u64_histogram(stringify!($name)) + .with_boundaries($crate::metrics::f64_histogram_buckets()) + .build() + }); }; ($name:ident, $meter:ident, $description:literal) => { + #[doc = $description] pub(crate) static $name: once_cell::sync::Lazy< $crate::opentelemetry::metrics::Histogram, - > = once_cell::sync::Lazy::new(|| $meter.u64_histogram($description).init()); + > = once_cell::sync::Lazy::new(|| { + $meter + .u64_histogram(stringify!($name)) + .with_description($description) + .with_boundaries($crate::metrics::f64_histogram_buckets()) + .build() + }); }; } -/// Create a [`Histogram`][Histogram] i64 metric with the specified name and an optional description, -/// associated with the specified meter. Note that the meter must be to a valid [`Meter`][Meter]. +/// Create a [`Gauge`][Gauge] metric with the specified name and an optional description, +/// associated with the specified meter. Note that the meter must be a valid [`Meter`][Meter]. /// -/// [Histogram]: opentelemetry::metrics::Histogram +/// [Gauge]: opentelemetry::metrics::Gauge /// [Meter]: opentelemetry::metrics::Meter #[macro_export] -macro_rules! histogram_metric_i64 { +macro_rules! gauge_metric { ($name:ident, $meter:ident) => { - pub(crate) static $name: once_cell::sync::Lazy< - $crate::opentelemetry::metrics::Histogram, - > = once_cell::sync::Lazy::new(|| $meter.i64_histogram(stringify!($name)).init()); + pub(crate) static $name: once_cell::sync::Lazy<$crate::opentelemetry::metrics::Gauge> = + once_cell::sync::Lazy::new(|| $meter.u64_gauge(stringify!($name)).build()); }; - ($name:ident, $meter:ident, $description:literal) => { - pub(crate) static $name: once_cell::sync::Lazy< - $crate::opentelemetry::metrics::Histogram, - > = once_cell::sync::Lazy::new(|| $meter.i64_histogram($description).init()); + ($name:ident, $meter:ident, description:literal) => { + #[doc = $description] + pub(crate) static $name: once_cell::sync::Lazy<$crate::opentelemetry::metrics::Gauge> = + once_cell::sync::Lazy::new(|| { + $meter + .u64_gauge(stringify!($name)) + .with_description($description) + .build() + }); }; } -/// Create a [`ObservableGauge`][ObservableGauge] metric with the specified name and an optional description, -/// associated with the specified meter. Note that the meter must be to a valid [`Meter`][Meter]. -/// -/// [ObservableGauge]: opentelemetry::metrics::ObservableGauge -/// [Meter]: opentelemetry::metrics::Meter +/// Create attributes to associate with a metric from key-value pairs. #[macro_export] -macro_rules! gauge_metric { - ($name:ident, $meter:ident) => { - pub(crate) static $name: once_cell::sync::Lazy< - $crate::opentelemetry::metrics::ObservableGauge, - > = once_cell::sync::Lazy::new(|| $meter.u64_observable_gauge(stringify!($name)).init()); - }; - ($name:ident, $meter:ident, description:literal) => { - pub(crate) static $name: once_cell::sync::Lazy< - $crate::opentelemetry::metrics::ObservableGauge, - > = once_cell::sync::Lazy::new(|| $meter.u64_observable_gauge($description).init()); +macro_rules! metric_attributes { + ($(($key:expr, $value:expr $(,)?)),+ $(,)?) => { + &[$($crate::opentelemetry::KeyValue::new($key, $value)),+] }; } -pub use helpers::add_attributes; +pub use helpers::f64_histogram_buckets; mod helpers { - pub fn add_attributes(attributes: U) -> Vec - where - T: Into, - U: IntoIterator, - { - attributes - .into_iter() - .map(|(key, value)| opentelemetry::KeyValue::new(key, value)) - .collect::>() + /// Returns the buckets to be used for a f64 histogram + #[inline(always)] + pub fn f64_histogram_buckets() -> Vec { + let mut init = 0.01; + let mut buckets: [f64; 15] = [0.0; 15]; + + for bucket in &mut buckets { + init *= 2.0; + *bucket = init; + } + + Vec::from(buckets) } } diff --git a/crates/router_env/tests/logger.rs b/crates/router_env/tests/logger.rs index 46b5b9538cf7..1070938a8a10 100644 --- a/crates/router_env/tests/logger.rs +++ b/crates/router_env/tests/logger.rs @@ -1,10 +1,11 @@ #![allow(clippy::unwrap_used)] mod test_module; + use ::config::ConfigError; use router_env::TelemetryGuard; -use self::test_module::some_module::*; +use self::test_module::fn_with_colon; fn logger() -> error_stack::Result<&'static TelemetryGuard, ConfigError> { use once_cell::sync::OnceCell; diff --git a/crates/router_env/tests/test_module/some_module.rs b/crates/router_env/tests/test_module.rs similarity index 90% rename from crates/router_env/tests/test_module/some_module.rs rename to crates/router_env/tests/test_module.rs index 8c9dda2c08e6..f3c382706b4d 100644 --- a/crates/router_env/tests/test_module/some_module.rs +++ b/crates/router_env/tests/test_module.rs @@ -1,7 +1,6 @@ -use logger::instrument; use router_env as logger; -#[instrument(skip_all)] +#[tracing::instrument(skip_all)] pub async fn fn_with_colon(val: i32) { let a = 13; let b = 31; @@ -23,7 +22,7 @@ pub async fn fn_with_colon(val: i32) { fn_without_colon(131).await; } -#[instrument(fields(val3 = "abc"), skip_all)] +#[tracing::instrument(fields(val3 = "abc"), skip_all)] pub async fn fn_without_colon(val: i32) { let a = 13; let b = 31; diff --git a/crates/router_env/tests/test_module/mod.rs b/crates/router_env/tests/test_module/mod.rs deleted file mode 100644 index 7699174549da..000000000000 --- a/crates/router_env/tests/test_module/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod some_module; diff --git a/crates/scheduler/Cargo.toml b/crates/scheduler/Cargo.toml index 0ffd97c2f0eb..24e8c63f9557 100644 --- a/crates/scheduler/Cargo.toml +++ b/crates/scheduler/Cargo.toml @@ -10,7 +10,7 @@ default = ["kv_store", "olap"] olap = ["storage_impl/olap", "hyperswitch_domain_models/olap"] kv_store = [] email = ["external_services/email"] -v1 = ["diesel_models/v1", "hyperswitch_domain_models/v1", "storage_impl/v1"] +v1 = ["diesel_models/v1", "hyperswitch_domain_models/v1", "storage_impl/v1", "common_utils/v1"] [dependencies] # Third party crates diff --git a/crates/scheduler/src/consumer.rs b/crates/scheduler/src/consumer.rs index 5791edd31b05..99b9df524ae0 100644 --- a/crates/scheduler/src/consumer.rs +++ b/crates/scheduler/src/consumer.rs @@ -7,7 +7,7 @@ use std::{ pub mod types; pub mod workflows; -use common_utils::{errors::CustomResult, signals::get_allowed_signals}; +use common_utils::{errors::CustomResult, id_type, signals::get_allowed_signals}; use diesel_models::enums; pub use diesel_models::{self, process_tracker as storage}; use error_stack::ResultExt; @@ -42,7 +42,7 @@ pub async fn start_consumer CustomResult<(), errors::ProcessTrackerError> where - F: Fn(&T, &str) -> CustomResult, + F: Fn(&T, &id_type::TenantId) -> CustomResult, { use std::time::Duration; @@ -88,7 +88,7 @@ where let start_time = std_time::Instant::now(); let tenants = state.get_tenants(); for tenant in tenants { - let session_state = app_state_to_session_state(state, tenant.as_str())?; + let session_state = app_state_to_session_state(state, &tenant)?; pt_utils::consumer_operation_handler( session_state.clone(), settings.clone(), @@ -165,7 +165,7 @@ pub async fn consumer_operations( pt_utils::add_histogram_metrics(&pickup_time, task, &stream_name); - metrics::TASK_CONSUMED.add(&metrics::CONTEXT, 1, &[]); + metrics::TASK_CONSUMED.add(1, &[]); handler.push(tokio::task::spawn(start_workflow( state.clone(), @@ -244,7 +244,7 @@ where .inspect_err(|error| { logger::error!(?error, "Failed to trigger workflow"); }); - metrics::TASK_PROCESSED.add(&metrics::CONTEXT, 1, &[]); + metrics::TASK_PROCESSED.add(1, &[]); res } diff --git a/crates/scheduler/src/db/mod.rs b/crates/scheduler/src/db.rs similarity index 100% rename from crates/scheduler/src/db/mod.rs rename to crates/scheduler/src/db.rs diff --git a/crates/scheduler/src/db/process_tracker.rs b/crates/scheduler/src/db/process_tracker.rs index c73b53b608c2..1b23ff1b5579 100644 --- a/crates/scheduler/src/db/process_tracker.rs +++ b/crates/scheduler/src/db/process_tracker.rs @@ -149,7 +149,7 @@ impl ProcessTrackerInterface for Store { this: storage::ProcessTracker, schedule_time: PrimitiveDateTime, ) -> CustomResult<(), errors::StorageError> { - metrics::TASK_RETRIED.add(&metrics::CONTEXT, 1, &[]); + metrics::TASK_RETRIED.add(1, &[]); let retry_count = this.retry_count + 1; self.update_process( this, @@ -177,7 +177,7 @@ impl ProcessTrackerInterface for Store { ) .await .attach_printable("Failed to update business status of process")?; - metrics::TASK_FINISHED.add(&metrics::CONTEXT, 1, &[]); + metrics::TASK_FINISHED.add(1, &[]); Ok(()) } diff --git a/crates/scheduler/src/metrics.rs b/crates/scheduler/src/metrics.rs index ca4fb9ec2424..27ac860d0794 100644 --- a/crates/scheduler/src/metrics.rs +++ b/crates/scheduler/src/metrics.rs @@ -1,9 +1,8 @@ -use router_env::{counter_metric, global_meter, histogram_metric, metrics_context}; +use router_env::{counter_metric, global_meter, histogram_metric_f64}; -metrics_context!(CONTEXT); global_meter!(PT_METER, "PROCESS_TRACKER"); -histogram_metric!(CONSUMER_STATS, PT_METER, "CONSUMER_OPS"); +histogram_metric_f64!(CONSUMER_OPS, PT_METER); counter_metric!(PAYMENT_COUNT, PT_METER); // No. of payments created counter_metric!(TASKS_PICKED_COUNT, PT_METER); // Tasks picked by diff --git a/crates/scheduler/src/producer.rs b/crates/scheduler/src/producer.rs index 6f710f55a341..3d28ec91fab8 100644 --- a/crates/scheduler/src/producer.rs +++ b/crates/scheduler/src/producer.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use common_utils::errors::CustomResult; +use common_utils::{errors::CustomResult, id_type}; use diesel_models::enums::ProcessTrackerStatus; use error_stack::{report, ResultExt}; use router_env::{ @@ -27,7 +27,7 @@ pub async fn start_producer( app_state_to_session_state: F, ) -> CustomResult<(), errors::ProcessTrackerError> where - F: Fn(&T, &str) -> CustomResult, + F: Fn(&T, &id_type::TenantId) -> CustomResult, T: SchedulerAppState, U: SchedulerSessionState, { @@ -69,7 +69,7 @@ where interval.tick().await; let tenants = state.get_tenants(); for tenant in tenants { - let session_state = app_state_to_session_state(state, tenant.as_str())?; + let session_state = app_state_to_session_state(state, &tenant)?; match run_producer_flow(&session_state, &scheduler_settings).await { Ok(_) => (), Err(error) => { @@ -175,6 +175,6 @@ pub async fn fetch_producer_tasks( // Safety: Assuming we won't deal with more than `u64::MAX` tasks at once #[allow(clippy::as_conversions)] - metrics::TASKS_PICKED_COUNT.add(&metrics::CONTEXT, new_tasks.len() as u64, &[]); + metrics::TASKS_PICKED_COUNT.add(new_tasks.len() as u64, &[]); Ok(new_tasks) } diff --git a/crates/scheduler/src/scheduler.rs b/crates/scheduler/src/scheduler.rs index 39a45d02ba96..2685c6311eaa 100644 --- a/crates/scheduler/src/scheduler.rs +++ b/crates/scheduler/src/scheduler.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use common_utils::errors::CustomResult; +use common_utils::{errors::CustomResult, id_type}; use storage_impl::mock_db::MockDb; #[cfg(feature = "kv_store")] use storage_impl::KVRouterStore; @@ -52,7 +52,7 @@ impl SchedulerInterface for MockDb {} #[async_trait::async_trait] pub trait SchedulerAppState: Send + Sync + Clone { - fn get_tenants(&self) -> Vec; + fn get_tenants(&self) -> Vec; } #[async_trait::async_trait] pub trait SchedulerSessionState: Send + Sync + Clone { @@ -71,7 +71,7 @@ pub async fn start_process_tracker< app_state_to_session_state: F, ) -> CustomResult<(), errors::ProcessTrackerError> where - F: Fn(&T, &str) -> CustomResult, + F: Fn(&T, &id_type::TenantId) -> CustomResult, { match scheduler_flow { SchedulerFlow::Producer => { diff --git a/crates/scheduler/src/utils.rs b/crates/scheduler/src/utils.rs index 8c2424d5017e..89328479537e 100644 --- a/crates/scheduler/src/utils.rs +++ b/crates/scheduler/src/utils.rs @@ -5,7 +5,7 @@ use diesel_models::enums::{self, ProcessTrackerStatus}; pub use diesel_models::process_tracker as storage; use error_stack::{report, ResultExt}; use redis_interface::{RedisConnectionPool, RedisEntryId}; -use router_env::{instrument, opentelemetry, tracing}; +use router_env::{instrument, tracing}; use uuid::Uuid; use super::{ @@ -29,7 +29,7 @@ where let batches = divide(tasks, settings); // Safety: Assuming we won't deal with more than `u64::MAX` batches at once #[allow(clippy::as_conversions)] - metrics::BATCHES_CREATED.add(&metrics::CONTEXT, batches.len() as u64, &[]); // Metrics + metrics::BATCHES_CREATED.add(batches.len() as u64, &[]); // Metrics for batch in batches { let result = update_status_and_append(state, flow, batch).await; match result { @@ -209,7 +209,7 @@ pub async fn get_batches( } }; - metrics::BATCHES_CONSUMED.add(&metrics::CONTEXT, 1, &[]); + metrics::BATCHES_CONSUMED.add(1, &[]); let (batches, entry_ids): (Vec>, Vec>) = response.into_values().map(|entries| { entries.into_iter().try_fold( @@ -290,13 +290,9 @@ pub fn add_histogram_metrics( let pickup_schedule_delta = (*pickup_time - *schedule_time).as_seconds_f64(); logger::error!("Time delta for scheduled tasks: {pickup_schedule_delta} seconds"); let runner_name = runner.clone(); - metrics::CONSUMER_STATS.record( - &metrics::CONTEXT, + metrics::CONSUMER_OPS.record( pickup_schedule_delta, - &[opentelemetry::KeyValue::new( - stream_name.to_owned(), - runner_name, - )], + router_env::metric_attributes!((stream_name.to_owned(), runner_name)), ); }; } @@ -321,10 +317,10 @@ pub fn get_schedule_time( pub fn get_pm_schedule_time( mapping: process_data::PaymentMethodsPTMapping, - pm: &enums::PaymentMethod, + pm: enums::PaymentMethod, retry_count: i32, ) -> Option { - let mapping = match mapping.custom_pm_mapping.get(pm) { + let mapping = match mapping.custom_pm_mapping.get(&pm) { Some(map) => map.clone(), None => mapping.default_mapping, }; diff --git a/crates/storage_impl/Cargo.toml b/crates/storage_impl/Cargo.toml index 0a84d5b25fe0..ac3598cea58a 100644 --- a/crates/storage_impl/Cargo.toml +++ b/crates/storage_impl/Cargo.toml @@ -13,8 +13,8 @@ dynamic_routing = [] oltp = [] olap = ["hyperswitch_domain_models/olap"] payouts = ["hyperswitch_domain_models/payouts"] -v1 = ["api_models/v1", "diesel_models/v1", "hyperswitch_domain_models/v1"] -v2 = ["api_models/v2", "diesel_models/v2", "hyperswitch_domain_models/v2"] +v1 = ["api_models/v1", "diesel_models/v1", "hyperswitch_domain_models/v1", "common_utils/v1"] +v2 = ["api_models/v2", "diesel_models/v2", "hyperswitch_domain_models/v2", "common_utils/v2"] customer_v2 = ["api_models/customer_v2", "diesel_models/customer_v2", "hyperswitch_domain_models/customer_v2"] payment_methods_v2 = ["diesel_models/payment_methods_v2", "api_models/payment_methods_v2", "hyperswitch_domain_models/payment_methods_v2"] diff --git a/crates/storage_impl/src/errors.rs b/crates/storage_impl/src/errors.rs index 1cee96f49ebd..10d7f4dc8e82 100644 --- a/crates/storage_impl/src/errors.rs +++ b/crates/storage_impl/src/errors.rs @@ -292,3 +292,9 @@ pub enum HealthCheckLockerError { #[error("Failed to establish Locker connection")] FailedToCallLocker, } + +#[derive(Debug, Clone, thiserror::Error)] +pub enum HealthCheckGRPCServiceError { + #[error("Failed to establish connection with gRPC service")] + FailedToCallService, +} diff --git a/crates/storage_impl/src/lib.rs b/crates/storage_impl/src/lib.rs index 5954f4791a57..09bb42567df4 100644 --- a/crates/storage_impl/src/lib.rs +++ b/crates/storage_impl/src/lib.rs @@ -262,9 +262,9 @@ impl KVRouterStore { .change_context(RedisError::JsonSerializationFailed)?, ) .await - .map(|_| metrics::KV_PUSHED_TO_DRAINER.add(&metrics::CONTEXT, 1, &[])) + .map(|_| metrics::KV_PUSHED_TO_DRAINER.add(1, &[])) .inspect_err(|error| { - metrics::KV_FAILED_TO_PUSH_TO_DRAINER.add(&metrics::CONTEXT, 1, &[]); + metrics::KV_FAILED_TO_PUSH_TO_DRAINER.add(1, &[]); logger::error!(?error, "Failed to add entry in drainer stream"); }) .change_context(RedisError::StreamAppendFailed) @@ -280,7 +280,7 @@ pub trait DataModelExt { } pub(crate) fn diesel_error_to_data_error( - diesel_error: &diesel_models::errors::DatabaseError, + diesel_error: diesel_models::errors::DatabaseError, ) -> StorageError { match diesel_error { diesel_models::errors::DatabaseError::DatabaseConnectionError => { @@ -293,7 +293,7 @@ pub(crate) fn diesel_error_to_data_error( entity: "entity ", key: None, }, - _ => StorageError::DatabaseError(error_stack::report!(*diesel_error)), + _ => StorageError::DatabaseError(error_stack::report!(diesel_error)), } } @@ -479,7 +479,7 @@ impl UniqueConstraints for diesel_models::Customer { #[cfg(all(feature = "v2", feature = "customer_v2"))] impl UniqueConstraints for diesel_models::Customer { fn unique_constraints(&self) -> Vec { - vec![format!("customer_{}", self.id.clone())] + vec![format!("customer_{}", self.id.get_string_repr())] } fn table_name(&self) -> &str { "Customer" diff --git a/crates/storage_impl/src/lookup.rs b/crates/storage_impl/src/lookup.rs index 943ef1f36f73..2f8a743a81a7 100644 --- a/crates/storage_impl/src/lookup.rs +++ b/crates/storage_impl/src/lookup.rs @@ -44,7 +44,7 @@ impl ReverseLookupInterface for RouterStore { .await .change_context(errors::StorageError::DatabaseConnectionError)?; new.insert(&conn).await.map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) } @@ -58,7 +58,7 @@ impl ReverseLookupInterface for RouterStore { DieselReverseLookup::find_by_lookup_id(id, &conn) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) } @@ -93,17 +93,17 @@ impl ReverseLookupInterface for KVRouterStore { }; let redis_entry = kv::TypedSql { op: kv::DBOperation::Insert { - insertable: kv::Insertable::ReverseLookUp(new), + insertable: Box::new(kv::Insertable::ReverseLookUp(new)), }, }; - match kv_wrapper::( + match Box::pin(kv_wrapper::( self, KvOperation::SetNx(&created_rev_lookup, redis_entry), PartitionKey::CombinationKey { combination: &format!("reverse_lookup_{}", &created_rev_lookup.lookup_id), }, - ) + )) .await .map_err(|err| err.to_redis_failed_response(&created_rev_lookup.lookup_id))? .try_into_setnx() @@ -140,13 +140,13 @@ impl ReverseLookupInterface for KVRouterStore { storage_enums::MerchantStorageScheme::PostgresOnly => database_call().await, storage_enums::MerchantStorageScheme::RedisKv => { let redis_fut = async { - kv_wrapper( + Box::pin(kv_wrapper( self, KvOperation::::Get, PartitionKey::CombinationKey { combination: &format!("reverse_lookup_{id}"), }, - ) + )) .await? .try_into_get() }; diff --git a/crates/storage_impl/src/metrics.rs b/crates/storage_impl/src/metrics.rs index cb7a6b216e47..b0c0c70af0a5 100644 --- a/crates/storage_impl/src/metrics.rs +++ b/crates/storage_impl/src/metrics.rs @@ -1,6 +1,5 @@ -use router_env::{counter_metric, gauge_metric, global_meter, metrics_context}; +use router_env::{counter_metric, gauge_metric, global_meter}; -metrics_context!(CONTEXT); global_meter!(GLOBAL_METER, "ROUTER_API"); counter_metric!(KV_MISS, GLOBAL_METER); // No. of KV misses diff --git a/crates/storage_impl/src/mock_db.rs b/crates/storage_impl/src/mock_db.rs index b3358d898b24..b81676a6cc4f 100644 --- a/crates/storage_impl/src/mock_db.rs +++ b/crates/storage_impl/src/mock_db.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use diesel_models::{self as store}; +use diesel_models as store; use error_stack::ResultExt; use futures::lock::Mutex; use hyperswitch_domain_models::{ @@ -60,6 +60,7 @@ pub struct MockDb { pub user_key_store: Arc>>, pub user_authentication_methods: Arc>>, + pub themes: Arc>>, } impl MockDb { @@ -105,6 +106,7 @@ impl MockDb { roles: Default::default(), user_key_store: Default::default(), user_authentication_methods: Default::default(), + themes: Default::default(), }) } } diff --git a/crates/storage_impl/src/mock_db/payment_attempt.rs b/crates/storage_impl/src/mock_db/payment_attempt.rs index c37f9f6d3dac..0415625f7ebb 100644 --- a/crates/storage_impl/src/mock_db/payment_attempt.rs +++ b/crates/storage_impl/src/mock_db/payment_attempt.rs @@ -1,6 +1,6 @@ use common_utils::errors::CustomResult; #[cfg(feature = "v2")] -use common_utils::types::keymanager::KeyManagerState; +use common_utils::{id_type, types::keymanager::KeyManagerState}; use diesel_models::enums as storage_enums; #[cfg(feature = "v2")] use hyperswitch_domain_models::merchant_key_store::MerchantKeyStore; @@ -52,7 +52,7 @@ impl PaymentAttemptInterface for MockDb { _payment_method_type: Option>, _authentication_type: Option>, _merchanat_connector_id: Option>, - _profile_id_list: Option>, + _card_network: Option>, _storage_scheme: storage_enums::MerchantStorageScheme, ) -> CustomResult { Err(StorageError::MockDbError)? @@ -74,7 +74,7 @@ impl PaymentAttemptInterface for MockDb { &self, _key_manager_state: &KeyManagerState, _merchant_key_store: &MerchantKeyStore, - _attempt_id: &str, + _attempt_id: &id_type::GlobalAttemptId, _storage_scheme: storage_enums::MerchantStorageScheme, ) -> error_stack::Result { // [#172]: Implement function for `MockDb` @@ -103,6 +103,19 @@ impl PaymentAttemptInterface for MockDb { Err(StorageError::MockDbError)? } + #[cfg(feature = "v2")] + async fn find_payment_attempt_by_profile_id_connector_transaction_id( + &self, + _key_manager_state: &KeyManagerState, + _merchant_key_store: &MerchantKeyStore, + _profile_id: &id_type::ProfileId, + _connector_transaction_id: &str, + _storage_scheme: storage_enums::MerchantStorageScheme, + ) -> CustomResult { + // [#172]: Implement function for `MockDb` + Err(StorageError::MockDbError)? + } + #[cfg(feature = "v1")] async fn find_attempts_by_merchant_id_payment_id( &self, @@ -123,21 +136,17 @@ impl PaymentAttemptInterface for MockDb { ) -> CustomResult { let mut payment_attempts = self.payment_attempts.lock().await; let time = common_utils::date_time::now(); - let payment_attempt = payment_attempt.populate_derived_fields(); let payment_attempt = PaymentAttempt { payment_id: payment_attempt.payment_id, merchant_id: payment_attempt.merchant_id, attempt_id: payment_attempt.attempt_id, status: payment_attempt.status, - amount: payment_attempt.amount, net_amount: payment_attempt.net_amount, currency: payment_attempt.currency, save_to_locker: payment_attempt.save_to_locker, connector: payment_attempt.connector, error_message: payment_attempt.error_message, offer_amount: payment_attempt.offer_amount, - surcharge_amount: payment_attempt.surcharge_amount, - tax_amount: payment_attempt.tax_amount, payment_method_id: payment_attempt.payment_method_id, payment_method: payment_attempt.payment_method, connector_transaction_id: None, @@ -185,8 +194,7 @@ impl PaymentAttemptInterface for MockDb { customer_acceptance: payment_attempt.customer_acceptance, organization_id: payment_attempt.organization_id, profile_id: payment_attempt.profile_id, - shipping_cost: payment_attempt.shipping_cost, - order_tax_amount: payment_attempt.order_tax_amount, + connector_mandate_detail: payment_attempt.connector_mandate_detail, }; payment_attempts.push(payment_attempt.clone()); Ok(payment_attempt) @@ -231,7 +239,7 @@ impl PaymentAttemptInterface for MockDb { } #[cfg(feature = "v2")] - async fn update_payment_attempt_with_attempt_id( + async fn update_payment_attempt( &self, _key_manager_state: &KeyManagerState, _merchant_key_store: &MerchantKeyStore, @@ -246,7 +254,7 @@ impl PaymentAttemptInterface for MockDb { #[cfg(feature = "v1")] async fn find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( &self, - _connector_transaction_id: &str, + _connector_transaction_id: &common_utils::types::ConnectorTransactionId, _payment_id: &common_utils::id_type::PaymentId, _merchant_id: &common_utils::id_type::MerchantId, _storage_scheme: storage_enums::MerchantStorageScheme, diff --git a/crates/storage_impl/src/mock_db/payment_intent.rs b/crates/storage_impl/src/mock_db/payment_intent.rs index 22160d901f49..3a564d958e9f 100644 --- a/crates/storage_impl/src/mock_db/payment_intent.rs +++ b/crates/storage_impl/src/mock_db/payment_intent.rs @@ -95,6 +95,7 @@ impl PaymentIntentInterface for MockDb { Ok(new) } + #[cfg(feature = "v1")] // safety: only used for testing #[allow(clippy::unwrap_used)] async fn update_payment_intent( @@ -130,6 +131,20 @@ impl PaymentIntentInterface for MockDb { Ok(payment_intent.clone()) } + #[cfg(feature = "v2")] + // safety: only used for testing + #[allow(clippy::unwrap_used)] + async fn update_payment_intent( + &self, + state: &KeyManagerState, + this: PaymentIntent, + update: PaymentIntentUpdate, + key_store: &MerchantKeyStore, + _storage_scheme: storage_enums::MerchantStorageScheme, + ) -> CustomResult { + todo!() + } + #[cfg(feature = "v1")] // safety: only used for testing #[allow(clippy::unwrap_used)] diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 63f5a0ff9f82..06c7fefe85fa 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -1,6 +1,10 @@ #[cfg(feature = "v2")] use common_utils::types::keymanager::KeyManagerState; -use common_utils::{errors::CustomResult, fallback_reverse_lookup_not_found}; +use common_utils::{ + errors::CustomResult, + fallback_reverse_lookup_not_found, + types::{ConnectorTransactionId, ConnectorTransactionIdTrait}, +}; use diesel_models::{ enums::{ MandateAmountData as DieselMandateAmountData, MandateDataType as DieselMandateType, @@ -9,11 +13,12 @@ use diesel_models::{ kv, payment_attempt::{ PaymentAttempt as DieselPaymentAttempt, PaymentAttemptNew as DieselPaymentAttemptNew, - PaymentAttemptUpdate as DieselPaymentAttemptUpdate, }, reverse_lookup::{ReverseLookup, ReverseLookupNew}, }; use error_stack::ResultExt; +#[cfg(feature = "v1")] +use hyperswitch_domain_models::payments::payment_attempt::PaymentAttemptNew; #[cfg(feature = "v2")] use hyperswitch_domain_models::{ behaviour::{Conversion, ReverseConversion}, @@ -22,9 +27,7 @@ use hyperswitch_domain_models::{ use hyperswitch_domain_models::{ errors, mandates::{MandateAmountData, MandateDataType, MandateDetails}, - payments::payment_attempt::{ - PaymentAttempt, PaymentAttemptInterface, PaymentAttemptNew, PaymentAttemptUpdate, - }, + payments::payment_attempt::{PaymentAttempt, PaymentAttemptInterface, PaymentAttemptUpdate}, }; #[cfg(feature = "olap")] use hyperswitch_domain_models::{ @@ -57,7 +60,7 @@ impl PaymentAttemptInterface for RouterStore { .insert(&conn) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) .map(PaymentAttempt::from_storage_model) @@ -80,7 +83,7 @@ impl PaymentAttemptInterface for RouterStore { .insert(&conn) .await .map_err(|error| { - let new_error = diesel_error_to_data_error(error.current_context()); + let new_error = diesel_error_to_data_error(*error.current_context()); error.change_context(new_error) })? .convert( @@ -105,7 +108,7 @@ impl PaymentAttemptInterface for RouterStore { .update_with_attempt_id(&conn, payment_attempt.to_storage_model()) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) .map(PaymentAttempt::from_storage_model) @@ -113,7 +116,7 @@ impl PaymentAttemptInterface for RouterStore { #[cfg(feature = "v2")] #[instrument(skip_all)] - async fn update_payment_attempt_with_attempt_id( + async fn update_payment_attempt( &self, key_manager_state: &KeyManagerState, merchant_key_store: &MerchantKeyStore, @@ -132,7 +135,7 @@ impl PaymentAttemptInterface for RouterStore { ) .await .map_err(|error| { - let new_error = diesel_error_to_data_error(error.current_context()); + let new_error = diesel_error_to_data_error(*error.current_context()); error.change_context(new_error) })? .convert( @@ -148,7 +151,7 @@ impl PaymentAttemptInterface for RouterStore { #[instrument(skip_all)] async fn find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( &self, - connector_transaction_id: &str, + connector_transaction_id: &ConnectorTransactionId, payment_id: &common_utils::id_type::PaymentId, merchant_id: &common_utils::id_type::MerchantId, _storage_scheme: MerchantStorageScheme, @@ -162,7 +165,7 @@ impl PaymentAttemptInterface for RouterStore { ) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) .map(PaymentAttempt::from_storage_model) @@ -184,7 +187,7 @@ impl PaymentAttemptInterface for RouterStore { ) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) .map(PaymentAttempt::from_storage_model) @@ -206,14 +209,14 @@ impl PaymentAttemptInterface for RouterStore { ) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) .map(PaymentAttempt::from_storage_model) } - #[cfg(feature = "v1")] #[instrument(skip_all)] + #[cfg(feature = "v1")] async fn find_payment_attempt_by_merchant_id_connector_txn_id( &self, merchant_id: &common_utils::id_type::MerchantId, @@ -228,12 +231,42 @@ impl PaymentAttemptInterface for RouterStore { ) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) .map(PaymentAttempt::from_storage_model) } + #[instrument(skip_all)] + #[cfg(feature = "v2")] + async fn find_payment_attempt_by_profile_id_connector_transaction_id( + &self, + key_manager_state: &KeyManagerState, + merchant_key_store: &MerchantKeyStore, + profile_id: &common_utils::id_type::ProfileId, + connector_txn_id: &str, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + let conn = pg_connection_read(self).await?; + DieselPaymentAttempt::find_by_profile_id_connector_transaction_id( + &conn, + profile_id, + connector_txn_id, + ) + .await + .map_err(|er| { + let new_err = diesel_error_to_data_error(*er.current_context()); + er.change_context(new_err) + })? + .convert( + key_manager_state, + merchant_key_store.key.get_inner(), + merchant_key_store.merchant_id.clone().into(), + ) + .await + .change_context(errors::StorageError::DecryptionError) + } + #[cfg(feature = "v1")] #[instrument(skip_all)] async fn find_payment_attempt_by_payment_id_merchant_id_attempt_id( @@ -253,7 +286,7 @@ impl PaymentAttemptInterface for RouterStore { ) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) .map(PaymentAttempt::from_storage_model) @@ -280,7 +313,7 @@ impl PaymentAttemptInterface for RouterStore { DieselPaymentAttempt::get_filters_for_payments(&conn, intents.as_slice(), merchant_id) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) .map( @@ -319,7 +352,7 @@ impl PaymentAttemptInterface for RouterStore { ) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) .map(PaymentAttempt::from_storage_model) @@ -337,7 +370,7 @@ impl PaymentAttemptInterface for RouterStore { DieselPaymentAttempt::find_by_merchant_id_payment_id(&conn, merchant_id, payment_id) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) .map(|a| { @@ -360,7 +393,7 @@ impl PaymentAttemptInterface for RouterStore { DieselPaymentAttempt::find_by_merchant_id_attempt_id(&conn, merchant_id, attempt_id) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) .map(PaymentAttempt::from_storage_model) @@ -372,7 +405,7 @@ impl PaymentAttemptInterface for RouterStore { &self, key_manager_state: &KeyManagerState, merchant_key_store: &MerchantKeyStore, - attempt_id: &str, + attempt_id: &common_utils::id_type::GlobalAttemptId, _storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { let conn = pg_connection_read(self).await?; @@ -380,7 +413,7 @@ impl PaymentAttemptInterface for RouterStore { DieselPaymentAttempt::find_by_id(&conn, attempt_id) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) })? .convert( @@ -403,7 +436,7 @@ impl PaymentAttemptInterface for RouterStore { payment_method_type: Option>, authentication_type: Option>, merchant_connector_id: Option>, - profile_id_list: Option>, + card_network: Option>, _storage_scheme: MerchantStorageScheme, ) -> CustomResult { let conn = self @@ -426,12 +459,12 @@ impl PaymentAttemptInterface for RouterStore { payment_method, payment_method_type, authentication_type, - profile_id_list, merchant_connector_id, + card_network, ) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) } @@ -459,7 +492,6 @@ impl PaymentAttemptInterface for KVRouterStore { .await } MerchantStorageScheme::RedisKv => { - let payment_attempt = payment_attempt.populate_derived_fields(); let merchant_id = payment_attempt.merchant_id.clone(); let payment_id = payment_attempt.payment_id.clone(); let key = PartitionKey::MerchantIdPaymentId { @@ -472,15 +504,12 @@ impl PaymentAttemptInterface for KVRouterStore { merchant_id: payment_attempt.merchant_id.clone(), attempt_id: payment_attempt.attempt_id.clone(), status: payment_attempt.status, - amount: payment_attempt.amount, - net_amount: payment_attempt.net_amount, + net_amount: payment_attempt.net_amount.clone(), currency: payment_attempt.currency, save_to_locker: payment_attempt.save_to_locker, connector: payment_attempt.connector.clone(), error_message: payment_attempt.error_message.clone(), offer_amount: payment_attempt.offer_amount, - surcharge_amount: payment_attempt.surcharge_amount, - tax_amount: payment_attempt.tax_amount, payment_method_id: payment_attempt.payment_method_id.clone(), payment_method: payment_attempt.payment_method, connector_transaction_id: None, @@ -534,17 +563,16 @@ impl PaymentAttemptInterface for KVRouterStore { customer_acceptance: payment_attempt.customer_acceptance.clone(), organization_id: payment_attempt.organization_id.clone(), profile_id: payment_attempt.profile_id.clone(), - shipping_cost: payment_attempt.shipping_cost, - order_tax_amount: payment_attempt.order_tax_amount, + connector_mandate_detail: payment_attempt.connector_mandate_detail.clone(), }; let field = format!("pa_{}", created_attempt.attempt_id); let redis_entry = kv::TypedSql { op: kv::DBOperation::Insert { - insertable: kv::Insertable::PaymentAttempt( + insertable: Box::new(kv::Insertable::PaymentAttempt(Box::new( payment_attempt.to_storage_model(), - ), + ))), }, }; @@ -563,7 +591,7 @@ impl PaymentAttemptInterface for KVRouterStore { self.insert_reverse_lookup(reverse_lookup, storage_scheme) .await?; - match kv_wrapper::( + match Box::pin(kv_wrapper::( self, KvOperation::HSetNx( &field, @@ -571,7 +599,7 @@ impl PaymentAttemptInterface for KVRouterStore { redis_entry, ), key, - ) + )) .await .map_err(|err| err.to_redis_failed_response(&key_str))? .try_into_hsetnx() @@ -635,7 +663,7 @@ impl PaymentAttemptInterface for KVRouterStore { } MerchantStorageScheme::RedisKv => { let key_str = key.to_string(); - let old_connector_transaction_id = &this.connector_transaction_id; + let old_connector_transaction_id = &this.get_connector_payment_id(); let old_preprocessing_id = &this.preprocessing_step_id; let updated_attempt = PaymentAttempt::from_storage_model( payment_attempt @@ -649,18 +677,18 @@ impl PaymentAttemptInterface for KVRouterStore { let redis_entry = kv::TypedSql { op: kv::DBOperation::Update { - updatable: kv::Updateable::PaymentAttemptUpdate( + updatable: Box::new(kv::Updateable::PaymentAttemptUpdate(Box::new( kv::PaymentAttemptUpdateMems { orig: this.clone().to_storage_model(), update_data: payment_attempt.to_storage_model(), }, - ), + ))), }, }; match ( old_connector_transaction_id, - &updated_attempt.connector_transaction_id, + &updated_attempt.get_connector_payment_id(), ) { (None, Some(connector_transaction_id)) => { add_connector_txn_id_to_reverse_lookup( @@ -668,7 +696,7 @@ impl PaymentAttemptInterface for KVRouterStore { key_str.as_str(), &this.merchant_id, updated_attempt.attempt_id.as_str(), - connector_transaction_id.as_str(), + connector_transaction_id, storage_scheme, ) .await?; @@ -680,7 +708,7 @@ impl PaymentAttemptInterface for KVRouterStore { key_str.as_str(), &this.merchant_id, updated_attempt.attempt_id.as_str(), - connector_transaction_id.as_str(), + connector_transaction_id, storage_scheme, ) .await?; @@ -717,11 +745,11 @@ impl PaymentAttemptInterface for KVRouterStore { (_, _) => {} } - kv_wrapper::<(), _, _>( + Box::pin(kv_wrapper::<(), _, _>( self, KvOperation::Hset::((&field, redis_value), redis_entry), key, - ) + )) .await .change_context(errors::StorageError::KVError)? .try_into_hset() @@ -734,7 +762,7 @@ impl PaymentAttemptInterface for KVRouterStore { #[cfg(feature = "v2")] #[instrument(skip_all)] - async fn update_payment_attempt_with_attempt_id( + async fn update_payment_attempt( &self, key_manager_state: &KeyManagerState, merchant_key_store: &MerchantKeyStore, @@ -744,7 +772,7 @@ impl PaymentAttemptInterface for KVRouterStore { ) -> error_stack::Result { // Ignoring storage scheme for v2 implementation self.router_store - .update_payment_attempt_with_attempt_id( + .update_payment_attempt( key_manager_state, merchant_key_store, this, @@ -758,7 +786,7 @@ impl PaymentAttemptInterface for KVRouterStore { #[instrument(skip_all)] async fn find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id( &self, - connector_transaction_id: &str, + connector_transaction_id: &ConnectorTransactionId, payment_id: &common_utils::id_type::PaymentId, merchant_id: &common_utils::id_type::MerchantId, storage_scheme: MerchantStorageScheme, @@ -783,8 +811,9 @@ impl PaymentAttemptInterface for KVRouterStore { MerchantStorageScheme::RedisKv => { // We assume that PaymentAttempt <=> PaymentIntent is a one-to-one relation for now let lookup_id = format!( - "pa_conn_trans_{}_{connector_transaction_id}", - merchant_id.get_string_repr() + "pa_conn_trans_{}_{}", + merchant_id.get_string_repr(), + connector_transaction_id.get_id() ); let lookup = fallback_reverse_lookup_not_found!( self.get_lookup_by_lookup_id(&lookup_id, storage_scheme) @@ -805,7 +834,7 @@ impl PaymentAttemptInterface for KVRouterStore { Box::pin(try_redis_get_else_try_database_get( async { - kv_wrapper(self, KvOperation::::HGet(&lookup.sk_id), key).await?.try_into_hget() + Box::pin(kv_wrapper(self, KvOperation::::HGet(&lookup.sk_id), key)).await?.try_into_hget() }, || async {self.router_store.find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id(connector_transaction_id, payment_id, merchant_id, storage_scheme).await}, )) @@ -846,11 +875,11 @@ impl PaymentAttemptInterface for KVRouterStore { let pattern = "pa_*"; let redis_fut = async { - let kv_result = kv_wrapper::( + let kv_result = Box::pin(kv_wrapper::( self, KvOperation::::Scan(pattern), key, - ) + )) .await? .try_into_scan(); kv_result.and_then(|mut payment_attempts| { @@ -905,11 +934,11 @@ impl PaymentAttemptInterface for KVRouterStore { let pattern = "pa_*"; let redis_fut = async { - let kv_result = kv_wrapper::( + let kv_result = Box::pin(kv_wrapper::( self, KvOperation::::Scan(pattern), key, - ) + )) .await? .try_into_scan(); kv_result.and_then(|mut payment_attempts| { @@ -935,8 +964,29 @@ impl PaymentAttemptInterface for KVRouterStore { } } - #[cfg(feature = "v1")] + #[cfg(feature = "v2")] + async fn find_payment_attempt_by_profile_id_connector_transaction_id( + &self, + key_manager_state: &KeyManagerState, + merchant_key_store: &MerchantKeyStore, + profile_id: &common_utils::id_type::ProfileId, + connector_transaction_id: &str, + storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + // Ignoring storage scheme for v2 implementation + self.router_store + .find_payment_attempt_by_profile_id_connector_transaction_id( + key_manager_state, + merchant_key_store, + profile_id, + connector_transaction_id, + storage_scheme, + ) + .await + } + #[instrument(skip_all)] + #[cfg(feature = "v1")] async fn find_payment_attempt_by_merchant_id_connector_txn_id( &self, merchant_id: &common_utils::id_type::MerchantId, @@ -981,11 +1031,11 @@ impl PaymentAttemptInterface for KVRouterStore { }; Box::pin(try_redis_get_else_try_database_get( async { - kv_wrapper( + Box::pin(kv_wrapper( self, KvOperation::::HGet(&lookup.sk_id), key, - ) + )) .await? .try_into_hget() }, @@ -1038,9 +1088,13 @@ impl PaymentAttemptInterface for KVRouterStore { let field = format!("pa_{attempt_id}"); Box::pin(try_redis_get_else_try_database_get( async { - kv_wrapper(self, KvOperation::::HGet(&field), key) - .await? - .try_into_hget() + Box::pin(kv_wrapper( + self, + KvOperation::::HGet(&field), + key, + )) + .await? + .try_into_hget() }, || async { self.router_store @@ -1101,11 +1155,11 @@ impl PaymentAttemptInterface for KVRouterStore { }; Box::pin(try_redis_get_else_try_database_get( async { - kv_wrapper( + Box::pin(kv_wrapper( self, KvOperation::::HGet(&lookup.sk_id), key, - ) + )) .await? .try_into_hget() }, @@ -1130,7 +1184,7 @@ impl PaymentAttemptInterface for KVRouterStore { &self, key_manager_state: &KeyManagerState, merchant_key_store: &MerchantKeyStore, - attempt_id: &str, + attempt_id: &common_utils::id_type::GlobalAttemptId, storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { // Ignoring storage scheme for v2 implementation @@ -1190,11 +1244,11 @@ impl PaymentAttemptInterface for KVRouterStore { Box::pin(try_redis_get_else_try_database_get( async { - kv_wrapper( + Box::pin(kv_wrapper( self, KvOperation::::HGet(&lookup.sk_id), key, - ) + )) .await? .try_into_hget() }, @@ -1244,9 +1298,13 @@ impl PaymentAttemptInterface for KVRouterStore { }; Box::pin(try_redis_get_else_try_database_get( async { - kv_wrapper(self, KvOperation::::Scan("pa_*"), key) - .await? - .try_into_scan() + Box::pin(kv_wrapper( + self, + KvOperation::::Scan("pa_*"), + key, + )) + .await? + .try_into_scan() }, || async { self.router_store @@ -1287,7 +1345,7 @@ impl PaymentAttemptInterface for KVRouterStore { payment_method_type: Option>, authentication_type: Option>, merchant_connector_id: Option>, - profile_id_list: Option>, + card_network: Option>, storage_scheme: MerchantStorageScheme, ) -> CustomResult { self.router_store @@ -1299,7 +1357,7 @@ impl PaymentAttemptInterface for KVRouterStore { payment_method_type, authentication_type, merchant_connector_id, - profile_id_list, + card_network, storage_scheme, ) .await @@ -1374,23 +1432,28 @@ impl DataModelExt for PaymentAttempt { type StorageModel = DieselPaymentAttempt; fn to_storage_model(self) -> Self::StorageModel { + let (connector_transaction_id, connector_transaction_data) = self + .connector_transaction_id + .map(ConnectorTransactionId::form_id_and_data) + .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) + .unwrap_or((None, None)); DieselPaymentAttempt { payment_id: self.payment_id, merchant_id: self.merchant_id, attempt_id: self.attempt_id, status: self.status, - amount: self.amount, - net_amount: Some(self.net_amount), + amount: self.net_amount.get_order_amount(), + net_amount: Some(self.net_amount.get_total_amount()), 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, + surcharge_amount: self.net_amount.get_surcharge_amount(), + tax_amount: self.net_amount.get_tax_on_surcharge(), payment_method_id: self.payment_method_id, payment_method: self.payment_method, - connector_transaction_id: self.connector_transaction_id, + connector_transaction_id, capture_method: self.capture_method, capture_on: self.capture_on, confirm: self.confirm, @@ -1444,29 +1507,37 @@ impl DataModelExt for PaymentAttempt { customer_acceptance: self.customer_acceptance, organization_id: self.organization_id, profile_id: self.profile_id, - shipping_cost: self.shipping_cost, - order_tax_amount: self.order_tax_amount, + connector_transaction_data, + shipping_cost: self.net_amount.get_shipping_cost(), + order_tax_amount: self.net_amount.get_order_tax_amount(), + connector_mandate_detail: self.connector_mandate_detail, } } fn from_storage_model(storage_model: Self::StorageModel) -> Self { + let connector_transaction_id = storage_model + .get_optional_connector_transaction_id() + .cloned(); Self { - net_amount: storage_model.get_or_calculate_net_amount(), + net_amount: hyperswitch_domain_models::payments::payment_attempt::NetAmount::new( + storage_model.amount, + storage_model.shipping_cost, + storage_model.order_tax_amount, + storage_model.surcharge_amount, + storage_model.tax_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, 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, payment_method_id: storage_model.payment_method_id, payment_method: storage_model.payment_method, - connector_transaction_id: storage_model.connector_transaction_id, + connector_transaction_id, capture_method: storage_model.capture_method, capture_on: storage_model.capture_on, confirm: storage_model.confirm, @@ -1515,8 +1586,7 @@ impl DataModelExt for PaymentAttempt { customer_acceptance: storage_model.customer_acceptance, organization_id: storage_model.organization_id, profile_id: storage_model.profile_id, - shipping_cost: storage_model.shipping_cost, - order_tax_amount: storage_model.order_tax_amount, + connector_mandate_detail: storage_model.connector_mandate_detail, } } } @@ -1527,19 +1597,19 @@ 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_total_amount()), payment_id: self.payment_id, merchant_id: self.merchant_id, attempt_id: self.attempt_id, status: self.status, - amount: self.amount, + amount: self.net_amount.get_order_amount(), 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, + surcharge_amount: self.net_amount.get_surcharge_amount(), + tax_amount: self.net_amount.get_tax_on_surcharge(), payment_method_id: self.payment_method_id, payment_method: self.payment_method, capture_method: self.capture_method, @@ -1597,26 +1667,30 @@ impl DataModelExt for PaymentAttemptNew { customer_acceptance: self.customer_acceptance, organization_id: self.organization_id, profile_id: self.profile_id, - shipping_cost: self.shipping_cost, - order_tax_amount: self.order_tax_amount, + shipping_cost: self.net_amount.get_shipping_cost(), + order_tax_amount: self.net_amount.get_order_tax_amount(), + connector_mandate_detail: self.connector_mandate_detail, } } fn from_storage_model(storage_model: Self::StorageModel) -> Self { Self { - net_amount: storage_model.get_or_calculate_net_amount(), + net_amount: hyperswitch_domain_models::payments::payment_attempt::NetAmount::new( + storage_model.amount, + storage_model.shipping_cost, + storage_model.order_tax_amount, + storage_model.surcharge_amount, + storage_model.tax_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, 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, payment_method_id: storage_model.payment_method_id, payment_method: storage_model.payment_method, capture_method: storage_model.capture_method, @@ -1667,725 +1741,7 @@ impl DataModelExt for PaymentAttemptNew { customer_acceptance: storage_model.customer_acceptance, organization_id: storage_model.organization_id, profile_id: storage_model.profile_id, - shipping_cost: storage_model.shipping_cost, - order_tax_amount: storage_model.order_tax_amount, - } - } -} - -#[cfg(feature = "v1")] -impl DataModelExt for PaymentAttemptUpdate { - type StorageModel = DieselPaymentAttemptUpdate; - - fn to_storage_model(self) -> Self::StorageModel { - match self { - Self::Update { - amount, - currency, - status, - authentication_type, - payment_method, - payment_token, - payment_method_data, - payment_method_type, - payment_experience, - business_sub_label, - amount_to_capture, - capture_method, - surcharge_amount, - tax_amount, - fingerprint_id, - payment_method_billing_address_id, - updated_by, - } => DieselPaymentAttemptUpdate::Update { - amount, - currency, - status, - authentication_type, - payment_method, - payment_token, - payment_method_data, - payment_method_type, - payment_experience, - business_sub_label, - amount_to_capture, - capture_method, - surcharge_amount, - tax_amount, - fingerprint_id, - payment_method_billing_address_id, - updated_by, - }, - Self::UpdateTrackers { - payment_token, - connector, - straight_through_algorithm, - amount_capturable, - updated_by, - surcharge_amount, - tax_amount, - merchant_connector_id, - } => DieselPaymentAttemptUpdate::UpdateTrackers { - payment_token, - connector, - straight_through_algorithm, - amount_capturable, - surcharge_amount, - tax_amount, - updated_by, - merchant_connector_id, - }, - Self::AuthenticationTypeUpdate { - authentication_type, - updated_by, - } => DieselPaymentAttemptUpdate::AuthenticationTypeUpdate { - authentication_type, - updated_by, - }, - Self::BlocklistUpdate { - status, - error_code, - error_message, - updated_by, - } => DieselPaymentAttemptUpdate::BlocklistUpdate { - status, - error_code, - error_message, - updated_by, - }, - Self::PaymentMethodDetailsUpdate { - payment_method_id, - updated_by, - } => DieselPaymentAttemptUpdate::PaymentMethodDetailsUpdate { - payment_method_id, - updated_by, - }, - Self::ConfirmUpdate { - amount, - currency, - status, - authentication_type, - capture_method, - payment_method, - browser_info, - connector, - payment_token, - payment_method_data, - payment_method_type, - payment_experience, - business_sub_label, - straight_through_algorithm, - error_code, - error_message, - amount_capturable, - surcharge_amount, - tax_amount, - fingerprint_id, - updated_by, - merchant_connector_id: connector_id, - payment_method_id, - external_three_ds_authentication_attempted, - authentication_connector, - authentication_id, - payment_method_billing_address_id, - client_source, - client_version, - customer_acceptance, - shipping_cost, - order_tax_amount, - } => DieselPaymentAttemptUpdate::ConfirmUpdate { - amount, - currency, - status, - authentication_type, - capture_method, - payment_method, - browser_info, - connector, - payment_token, - payment_method_data, - payment_method_type, - payment_experience, - business_sub_label, - straight_through_algorithm, - error_code, - error_message, - amount_capturable, - surcharge_amount, - tax_amount, - fingerprint_id, - updated_by, - merchant_connector_id: connector_id, - payment_method_id, - external_three_ds_authentication_attempted, - authentication_connector, - authentication_id, - payment_method_billing_address_id, - client_source, - client_version, - customer_acceptance, - shipping_cost, - order_tax_amount, - }, - Self::VoidUpdate { - status, - cancellation_reason, - updated_by, - } => DieselPaymentAttemptUpdate::VoidUpdate { - status, - cancellation_reason, - updated_by, - }, - Self::ResponseUpdate { - status, - connector, - connector_transaction_id, - authentication_type, - payment_method_id, - mandate_id, - connector_metadata, - payment_token, - error_code, - error_message, - error_reason, - connector_response_reference_id, - amount_capturable, - updated_by, - authentication_data, - encoded_data, - unified_code, - unified_message, - payment_method_data, - charge_id, - } => DieselPaymentAttemptUpdate::ResponseUpdate { - status, - connector, - connector_transaction_id, - authentication_type, - payment_method_id, - mandate_id, - connector_metadata, - payment_token, - error_code, - error_message, - error_reason, - connector_response_reference_id, - amount_capturable, - updated_by, - authentication_data, - encoded_data, - unified_code, - unified_message, - payment_method_data, - charge_id, - }, - Self::UnresolvedResponseUpdate { - status, - connector, - connector_transaction_id, - payment_method_id, - error_code, - error_message, - error_reason, - connector_response_reference_id, - updated_by, - } => DieselPaymentAttemptUpdate::UnresolvedResponseUpdate { - status, - connector, - connector_transaction_id, - payment_method_id, - error_code, - error_message, - error_reason, - connector_response_reference_id, - updated_by, - }, - Self::StatusUpdate { status, updated_by } => { - DieselPaymentAttemptUpdate::StatusUpdate { status, updated_by } - } - Self::ErrorUpdate { - connector, - status, - error_code, - error_message, - error_reason, - amount_capturable, - updated_by, - unified_code, - unified_message, - connector_transaction_id, - payment_method_data, - authentication_type, - } => DieselPaymentAttemptUpdate::ErrorUpdate { - connector, - status, - error_code, - error_message, - error_reason, - amount_capturable, - updated_by, - unified_code, - unified_message, - connector_transaction_id, - payment_method_data, - authentication_type, - }, - Self::CaptureUpdate { - multiple_capture_count, - updated_by, - amount_to_capture, - } => DieselPaymentAttemptUpdate::CaptureUpdate { - multiple_capture_count, - updated_by, - amount_to_capture, - }, - Self::PreprocessingUpdate { - status, - payment_method_id, - connector_metadata, - preprocessing_step_id, - connector_transaction_id, - connector_response_reference_id, - updated_by, - } => DieselPaymentAttemptUpdate::PreprocessingUpdate { - status, - payment_method_id, - connector_metadata, - preprocessing_step_id, - connector_transaction_id, - connector_response_reference_id, - updated_by, - }, - Self::RejectUpdate { - status, - error_code, - error_message, - updated_by, - } => DieselPaymentAttemptUpdate::RejectUpdate { - status, - error_code, - error_message, - updated_by, - }, - Self::AmountToCaptureUpdate { - status, - amount_capturable, - updated_by, - } => DieselPaymentAttemptUpdate::AmountToCaptureUpdate { - status, - amount_capturable, - updated_by, - }, - Self::ConnectorResponse { - authentication_data, - 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, - }, - Self::AuthenticationUpdate { - status, - external_three_ds_authentication_attempted, - authentication_connector, - authentication_id, - updated_by, - } => DieselPaymentAttemptUpdate::AuthenticationUpdate { - status, - external_three_ds_authentication_attempted, - authentication_connector, - authentication_id, - updated_by, - }, - Self::ManualUpdate { - status, - error_code, - error_message, - error_reason, - updated_by, - unified_code, - unified_message, - connector_transaction_id, - } => DieselPaymentAttemptUpdate::ManualUpdate { - status, - error_code, - error_message, - error_reason, - updated_by, - unified_code, - unified_message, - connector_transaction_id, - }, - } - } - - fn from_storage_model(storage_model: Self::StorageModel) -> Self { - match storage_model { - DieselPaymentAttemptUpdate::Update { - amount, - currency, - status, - authentication_type, - payment_method, - payment_token, - payment_method_data, - payment_method_type, - payment_experience, - business_sub_label, - amount_to_capture, - capture_method, - surcharge_amount, - tax_amount, - fingerprint_id, - updated_by, - payment_method_billing_address_id, - } => Self::Update { - amount, - currency, - status, - authentication_type, - payment_method, - payment_token, - payment_method_data, - payment_method_type, - payment_experience, - business_sub_label, - amount_to_capture, - capture_method, - surcharge_amount, - tax_amount, - fingerprint_id, - payment_method_billing_address_id, - updated_by, - }, - DieselPaymentAttemptUpdate::UpdateTrackers { - payment_token, - connector, - straight_through_algorithm, - amount_capturable, - updated_by, - surcharge_amount, - tax_amount, - merchant_connector_id: connector_id, - } => Self::UpdateTrackers { - payment_token, - connector, - straight_through_algorithm, - amount_capturable, - surcharge_amount, - tax_amount, - updated_by, - merchant_connector_id: connector_id, - }, - DieselPaymentAttemptUpdate::AuthenticationTypeUpdate { - authentication_type, - updated_by, - } => Self::AuthenticationTypeUpdate { - authentication_type, - updated_by, - }, - DieselPaymentAttemptUpdate::ConfirmUpdate { - amount, - currency, - status, - authentication_type, - capture_method, - payment_method, - browser_info, - connector, - payment_token, - payment_method_data, - payment_method_type, - payment_experience, - business_sub_label, - straight_through_algorithm, - error_code, - error_message, - amount_capturable, - surcharge_amount, - tax_amount, - fingerprint_id, - updated_by, - merchant_connector_id: connector_id, - payment_method_id, - external_three_ds_authentication_attempted, - authentication_connector, - authentication_id, - payment_method_billing_address_id, - client_source, - client_version, - customer_acceptance, - shipping_cost, - order_tax_amount, - } => Self::ConfirmUpdate { - amount, - currency, - status, - authentication_type, - capture_method, - payment_method, - browser_info, - connector, - payment_token, - payment_method_data, - payment_method_type, - payment_experience, - business_sub_label, - straight_through_algorithm, - error_code, - error_message, - amount_capturable, - surcharge_amount, - tax_amount, - fingerprint_id, - updated_by, - merchant_connector_id: connector_id, - payment_method_id, - external_three_ds_authentication_attempted, - authentication_connector, - authentication_id, - payment_method_billing_address_id, - client_source, - client_version, - customer_acceptance, - shipping_cost, - order_tax_amount, - }, - DieselPaymentAttemptUpdate::VoidUpdate { - status, - cancellation_reason, - updated_by, - } => Self::VoidUpdate { - status, - cancellation_reason, - updated_by, - }, - DieselPaymentAttemptUpdate::BlocklistUpdate { - status, - error_code, - error_message, - updated_by, - } => Self::BlocklistUpdate { - status, - error_code, - error_message, - updated_by, - }, - DieselPaymentAttemptUpdate::PaymentMethodDetailsUpdate { - payment_method_id, - updated_by, - } => Self::PaymentMethodDetailsUpdate { - payment_method_id, - updated_by, - }, - DieselPaymentAttemptUpdate::ResponseUpdate { - status, - connector, - connector_transaction_id, - authentication_type, - payment_method_id, - mandate_id, - connector_metadata, - payment_token, - error_code, - error_message, - error_reason, - connector_response_reference_id, - amount_capturable, - updated_by, - authentication_data, - encoded_data, - unified_code, - unified_message, - payment_method_data, - charge_id, - } => Self::ResponseUpdate { - status, - connector, - connector_transaction_id, - authentication_type, - payment_method_id, - mandate_id, - connector_metadata, - payment_token, - error_code, - error_message, - error_reason, - connector_response_reference_id, - amount_capturable, - updated_by, - authentication_data, - encoded_data, - unified_code, - unified_message, - payment_method_data, - charge_id, - }, - DieselPaymentAttemptUpdate::UnresolvedResponseUpdate { - status, - connector, - connector_transaction_id, - payment_method_id, - error_code, - error_message, - error_reason, - connector_response_reference_id, - updated_by, - } => Self::UnresolvedResponseUpdate { - status, - connector, - connector_transaction_id, - payment_method_id, - error_code, - error_message, - error_reason, - connector_response_reference_id, - updated_by, - }, - DieselPaymentAttemptUpdate::StatusUpdate { status, updated_by } => { - Self::StatusUpdate { status, updated_by } - } - DieselPaymentAttemptUpdate::ErrorUpdate { - connector, - status, - error_code, - error_message, - error_reason, - amount_capturable, - updated_by, - unified_code, - unified_message, - connector_transaction_id, - payment_method_data, - authentication_type, - } => Self::ErrorUpdate { - connector, - status, - error_code, - error_message, - error_reason, - amount_capturable, - updated_by, - unified_code, - unified_message, - connector_transaction_id, - payment_method_data, - authentication_type, - }, - DieselPaymentAttemptUpdate::CaptureUpdate { - amount_to_capture, - multiple_capture_count, - updated_by, - } => Self::CaptureUpdate { - amount_to_capture, - multiple_capture_count, - updated_by, - }, - DieselPaymentAttemptUpdate::PreprocessingUpdate { - status, - payment_method_id, - connector_metadata, - preprocessing_step_id, - connector_transaction_id, - connector_response_reference_id, - updated_by, - } => Self::PreprocessingUpdate { - status, - payment_method_id, - connector_metadata, - preprocessing_step_id, - connector_transaction_id, - connector_response_reference_id, - updated_by, - }, - DieselPaymentAttemptUpdate::RejectUpdate { - status, - error_code, - error_message, - updated_by, - } => Self::RejectUpdate { - status, - error_code, - error_message, - updated_by, - }, - DieselPaymentAttemptUpdate::AmountToCaptureUpdate { - status, - amount_capturable, - updated_by, - } => Self::AmountToCaptureUpdate { - status, - amount_capturable, - updated_by, - }, - DieselPaymentAttemptUpdate::ConnectorResponse { - authentication_data, - 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, - }, - DieselPaymentAttemptUpdate::AuthenticationUpdate { - status, - external_three_ds_authentication_attempted, - authentication_connector, - authentication_id, - updated_by, - } => Self::AuthenticationUpdate { - status, - external_three_ds_authentication_attempted, - authentication_connector, - authentication_id, - updated_by, - }, - DieselPaymentAttemptUpdate::ManualUpdate { - status, - error_code, - error_message, - error_reason, - updated_by, - unified_code, - unified_message, - connector_transaction_id, - } => Self::ManualUpdate { - status, - error_code, - error_message, - error_reason, - updated_by, - unified_code, - unified_message, - connector_transaction_id, - }, + connector_mandate_detail: storage_model.connector_mandate_detail, } } } diff --git a/crates/storage_impl/src/payments/payment_intent.rs b/crates/storage_impl/src/payments/payment_intent.rs index 4c7232f6f3f5..786cbe75a43d 100644 --- a/crates/storage_impl/src/payments/payment_intent.rs +++ b/crates/storage_impl/src/payments/payment_intent.rs @@ -10,6 +10,8 @@ use common_utils::{ }; #[cfg(feature = "olap")] use diesel::{associations::HasTable, ExpressionMethods, JoinOnDsl, QueryDsl}; +#[cfg(feature = "v1")] +use diesel_models::payment_intent::PaymentIntentUpdate as DieselPaymentIntentUpdate; #[cfg(feature = "olap")] use diesel_models::query::generics::db_metrics; #[cfg(all(feature = "v1", feature = "olap"))] @@ -23,11 +25,7 @@ use diesel_models::schema_v2::{ payment_intent::dsl as pi_dsl, }; use diesel_models::{ - enums::MerchantStorageScheme, - kv, - payment_intent::{ - PaymentIntent as DieselPaymentIntent, PaymentIntentUpdate as DieselPaymentIntentUpdate, - }, + enums::MerchantStorageScheme, kv, payment_intent::PaymentIntent as DieselPaymentIntent, }; use error_stack::ResultExt; #[cfg(feature = "olap")] @@ -103,7 +101,9 @@ impl PaymentIntentInterface for KVRouterStore { let redis_entry = kv::TypedSql { op: kv::DBOperation::Insert { - insertable: kv::Insertable::PaymentIntent(new_payment_intent), + insertable: Box::new(kv::Insertable::PaymentIntent(Box::new( + new_payment_intent, + ))), }, }; @@ -113,7 +113,7 @@ impl PaymentIntentInterface for KVRouterStore { .await .change_context(StorageError::EncryptionError)?; - match kv_wrapper::( + match Box::pin(kv_wrapper::( self, KvOperation::::HSetNx( &field, @@ -121,7 +121,7 @@ impl PaymentIntentInterface for KVRouterStore { redis_entry, ), key, - ) + )) .await .map_err(|err| err.to_redis_failed_response(&key_str))? .try_into_hsetnx() @@ -219,20 +219,20 @@ impl PaymentIntentInterface for KVRouterStore { let redis_entry = kv::TypedSql { op: kv::DBOperation::Update { - updatable: kv::Updateable::PaymentIntentUpdate( + updatable: Box::new(kv::Updateable::PaymentIntentUpdate(Box::new( kv::PaymentIntentUpdateMems { orig: origin_diesel_intent, update_data: diesel_intent_update, }, - ), + ))), }, }; - kv_wrapper::<(), _, _>( + Box::pin(kv_wrapper::<(), _, _>( self, KvOperation::::Hset((&field, redis_value), redis_entry), key, - ) + )) .await .map_err(|err| err.to_redis_failed_response(&key_str))? .try_into_hset() @@ -295,7 +295,7 @@ impl PaymentIntentInterface for KVRouterStore { DieselPaymentIntent::find_by_payment_id_merchant_id(&conn, payment_id, merchant_id) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) }; @@ -316,11 +316,11 @@ impl PaymentIntentInterface for KVRouterStore { let field = payment_id.get_hash_key_for_kv_store(); Box::pin(utils::try_redis_get_else_try_database_get( async { - kv_wrapper::( + Box::pin(kv_wrapper::( self, KvOperation::::HGet(&field), key, - ) + )) .await? .try_into_hget() }, @@ -356,7 +356,7 @@ impl PaymentIntentInterface for KVRouterStore { let diesel_payment_intent = DieselPaymentIntent::find_by_global_id(&conn, id) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) })?; @@ -479,7 +479,7 @@ impl PaymentIntentInterface for crate::RouterStore { .insert(&conn) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) })?; @@ -493,6 +493,7 @@ impl PaymentIntentInterface for crate::RouterStore { .change_context(StorageError::DecryptionError) } + #[cfg(feature = "v1")] #[instrument(skip_all)] async fn update_payment_intent( &self, @@ -512,7 +513,42 @@ impl PaymentIntentInterface for crate::RouterStore { .update(&conn, diesel_payment_intent_update) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); + er.change_context(new_err) + })?; + + PaymentIntent::convert_back( + state, + diesel_payment_intent, + merchant_key_store.key.get_inner(), + merchant_key_store.merchant_id.clone().into(), + ) + .await + .change_context(StorageError::DecryptionError) + } + + #[cfg(feature = "v2")] + #[instrument(skip_all)] + async fn update_payment_intent( + &self, + state: &KeyManagerState, + this: PaymentIntent, + payment_intent: PaymentIntentUpdate, + merchant_key_store: &MerchantKeyStore, + _storage_scheme: MerchantStorageScheme, + ) -> error_stack::Result { + let conn = pg_connection_write(self).await?; + let diesel_payment_intent_update = + diesel_models::PaymentIntentUpdateInternal::from(payment_intent); + + let diesel_payment_intent = this + .convert() + .await + .change_context(StorageError::EncryptionError)? + .update(&conn, diesel_payment_intent_update) + .await + .map_err(|er| { + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) })?; @@ -541,7 +577,7 @@ impl PaymentIntentInterface for crate::RouterStore { DieselPaymentIntent::find_by_payment_id_merchant_id(&conn, payment_id, merchant_id) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) .async_and_then(|diesel_payment_intent| async { @@ -570,7 +606,7 @@ impl PaymentIntentInterface for crate::RouterStore { let diesel_payment_intent = DieselPaymentIntent::find_by_global_id(&conn, id) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) })?; @@ -795,10 +831,11 @@ impl PaymentIntentInterface for crate::RouterStore { let conn = connection::pg_connection_read(self).await.switch()?; let conn = async_bb8_diesel::Connection::as_async_conn(&conn); let mut query = DieselPaymentIntent::table() + .filter(pi_dsl::merchant_id.eq(merchant_id.to_owned())) .inner_join( payment_attempt_schema::table.on(pa_dsl::attempt_id.eq(pi_dsl::active_attempt_id)), ) - .filter(pi_dsl::merchant_id.eq(merchant_id.to_owned())) + .filter(pa_dsl::merchant_id.eq(merchant_id.to_owned())) // Ensure merchant_ids match, as different merchants can share payment/attempt IDs. .into_boxed(); query = match constraints { @@ -833,6 +870,12 @@ impl PaymentIntentInterface for crate::RouterStore { query = query.filter(pi_dsl::customer_id.eq(customer_id.clone())); } + if let Some(merchant_order_reference_id) = ¶ms.merchant_order_reference_id { + query = query.filter( + pi_dsl::merchant_order_reference_id.eq(merchant_order_reference_id.clone()), + ) + } + if let Some(profile_id) = ¶ms.profile_id { query = query.filter(pi_dsl::profile_id.eq_any(profile_id.clone())); } @@ -939,6 +982,9 @@ impl PaymentIntentInterface for crate::RouterStore { None => query, }; + if let Some(card_network) = ¶ms.card_network { + query = query.filter(pa_dsl::card_network.eq_any(card_network.clone())); + } query } }; @@ -1000,6 +1046,11 @@ impl PaymentIntentInterface for crate::RouterStore { if let Some(customer_id) = ¶ms.customer_id { query = query.filter(pi_dsl::customer_id.eq(customer_id.clone())); } + if let Some(merchant_order_reference_id) = ¶ms.merchant_order_reference_id { + query = query.filter( + pi_dsl::merchant_order_reference_id.eq(merchant_order_reference_id.clone()), + ) + } if let Some(profile_id) = ¶ms.profile_id { query = query.filter(pi_dsl::profile_id.eq_any(profile_id.clone())); } diff --git a/crates/storage_impl/src/payouts/payout_attempt.rs b/crates/storage_impl/src/payouts/payout_attempt.rs index 33d73f99e5f7..caefa4eeda8c 100644 --- a/crates/storage_impl/src/payouts/payout_attempt.rs +++ b/crates/storage_impl/src/payouts/payout_attempt.rs @@ -93,9 +93,9 @@ impl PayoutAttemptInterface for KVRouterStore { let redis_entry = kv::TypedSql { op: kv::DBOperation::Insert { - insertable: kv::Insertable::PayoutAttempt( + insertable: Box::new(kv::Insertable::PayoutAttempt( new_payout_attempt.to_storage_model(), - ), + )), }, }; @@ -115,7 +115,7 @@ impl PayoutAttemptInterface for KVRouterStore { self.insert_reverse_lookup(reverse_lookup, storage_scheme) .await?; - match kv_wrapper::( + match Box::pin(kv_wrapper::( self, KvOperation::::HSetNx( &field, @@ -123,7 +123,7 @@ impl PayoutAttemptInterface for KVRouterStore { redis_entry, ), key, - ) + )) .await .map_err(|err| err.to_redis_failed_response(&key_str))? .try_into_hsetnx() @@ -182,12 +182,12 @@ impl PayoutAttemptInterface for KVRouterStore { let redis_entry = kv::TypedSql { op: kv::DBOperation::Update { - updatable: kv::Updateable::PayoutAttemptUpdate( + updatable: Box::new(kv::Updateable::PayoutAttemptUpdate( kv::PayoutAttemptUpdateMems { orig: origin_diesel_payout, update_data: diesel_payout_update, }, - ), + )), }, }; @@ -227,11 +227,11 @@ impl PayoutAttemptInterface for KVRouterStore { _ => {} } - kv_wrapper::<(), _, _>( + Box::pin(kv_wrapper::<(), _, _>( self, KvOperation::::Hset((&field, redis_value), redis_entry), key, - ) + )) .await .map_err(|err| err.to_redis_failed_response(&key_str))? .try_into_hset() @@ -284,11 +284,11 @@ impl PayoutAttemptInterface for KVRouterStore { }; Box::pin(utils::try_redis_get_else_try_database_get( async { - kv_wrapper( + Box::pin(kv_wrapper( self, KvOperation::::HGet(&lookup.sk_id), key, - ) + )) .await? .try_into_hget() }, @@ -345,11 +345,11 @@ impl PayoutAttemptInterface for KVRouterStore { }; Box::pin(utils::try_redis_get_else_try_database_get( async { - kv_wrapper( + Box::pin(kv_wrapper( self, KvOperation::::HGet(&lookup.sk_id), key, - ) + )) .await? .try_into_hget() }, @@ -395,7 +395,7 @@ impl PayoutAttemptInterface for crate::RouterStore { .insert(&conn) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) .map(PayoutAttempt::from_storage_model) @@ -415,7 +415,7 @@ impl PayoutAttemptInterface for crate::RouterStore { .update_with_attempt_id(&conn, payout.to_storage_model()) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) .map(PayoutAttempt::from_storage_model) @@ -437,7 +437,7 @@ impl PayoutAttemptInterface for crate::RouterStore { .await .map(PayoutAttempt::from_storage_model) .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) } @@ -458,7 +458,7 @@ impl PayoutAttemptInterface for crate::RouterStore { .await .map(PayoutAttempt::from_storage_model) .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) } @@ -479,7 +479,7 @@ impl PayoutAttemptInterface for crate::RouterStore { DieselPayoutAttempt::get_filters_for_payouts(&conn, payouts.as_slice(), merchant_id) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) .map( diff --git a/crates/storage_impl/src/payouts/payouts.rs b/crates/storage_impl/src/payouts/payouts.rs index 2a49b01458f7..0c69b2f24120 100644 --- a/crates/storage_impl/src/payouts/payouts.rs +++ b/crates/storage_impl/src/payouts/payouts.rs @@ -130,11 +130,11 @@ impl PayoutsInterface for KVRouterStore { let redis_entry = kv::TypedSql { op: kv::DBOperation::Insert { - insertable: kv::Insertable::Payouts(new.to_storage_model()), + insertable: Box::new(kv::Insertable::Payouts(new.to_storage_model())), }, }; - match kv_wrapper::( + match Box::pin(kv_wrapper::( self, KvOperation::::HSetNx( &field, @@ -142,7 +142,7 @@ impl PayoutsInterface for KVRouterStore { redis_entry, ), key, - ) + )) .await .map_err(|err| err.to_redis_failed_response(&key_str))? .try_into_hsetnx() @@ -201,18 +201,18 @@ impl PayoutsInterface for KVRouterStore { let redis_entry = kv::TypedSql { op: kv::DBOperation::Update { - updatable: kv::Updateable::PayoutsUpdate(kv::PayoutsUpdateMems { + updatable: Box::new(kv::Updateable::PayoutsUpdate(kv::PayoutsUpdateMems { orig: origin_diesel_payout, update_data: diesel_payout_update, - }), + })), }, }; - kv_wrapper::<(), _, _>( + Box::pin(kv_wrapper::<(), _, _>( self, KvOperation::::Hset((&field, redis_value), redis_entry), key, - ) + )) .await .map_err(|err| err.to_redis_failed_response(&key_str))? .try_into_hset() @@ -235,7 +235,7 @@ impl PayoutsInterface for KVRouterStore { DieselPayouts::find_by_merchant_id_payout_id(&conn, merchant_id, payout_id) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) }; @@ -255,11 +255,11 @@ impl PayoutsInterface for KVRouterStore { let field = format!("po_{payout_id}"); Box::pin(utils::try_redis_get_else_try_database_get( async { - kv_wrapper::( + Box::pin(kv_wrapper::( self, KvOperation::::HGet(&field), key, - ) + )) .await? .try_into_hget() }, @@ -283,7 +283,7 @@ impl PayoutsInterface for KVRouterStore { DieselPayouts::find_optional_by_merchant_id_payout_id(&conn, merchant_id, payout_id) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) }; @@ -312,11 +312,11 @@ impl PayoutsInterface for KVRouterStore { let field = format!("po_{payout_id}"); Box::pin(utils::try_redis_get_else_try_database_get( async { - kv_wrapper::( + Box::pin(kv_wrapper::( self, KvOperation::::HGet(&field), key, - ) + )) .await? .try_into_hget() .map(Some) @@ -423,7 +423,7 @@ impl PayoutsInterface for crate::RouterStore { .insert(&conn) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) .map(Payouts::from_storage_model) @@ -443,7 +443,7 @@ impl PayoutsInterface for crate::RouterStore { .update(&conn, payout.to_storage_model()) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) .map(Payouts::from_storage_model) @@ -461,7 +461,7 @@ impl PayoutsInterface for crate::RouterStore { .await .map(Payouts::from_storage_model) .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) } @@ -478,7 +478,7 @@ impl PayoutsInterface for crate::RouterStore { .await .map(|x| x.map(Payouts::from_storage_model)) .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) } @@ -814,7 +814,7 @@ impl PayoutsInterface for crate::RouterStore { ) .await .map_err(|er| { - let new_err = diesel_error_to_data_error(er.current_context()); + let new_err = diesel_error_to_data_error(*er.current_context()); er.change_context(new_err) }) } diff --git a/crates/storage_impl/src/redis/cache.rs b/crates/storage_impl/src/redis/cache.rs index 699310579594..93255fac9144 100644 --- a/crates/storage_impl/src/redis/cache.rs +++ b/crates/storage_impl/src/redis/cache.rs @@ -9,10 +9,7 @@ use error_stack::{Report, ResultExt}; use moka::future::Cache as MokaCache; use once_cell::sync::Lazy; use redis_interface::{errors::RedisError, RedisConnectionPool, RedisValue}; -use router_env::{ - metrics::add_attributes, - tracing::{self, instrument}, -}; +use router_env::tracing::{self, instrument}; use crate::{ errors::StorageError, @@ -72,7 +69,7 @@ pub static PM_FILTERS_CGRAPH_CACHE: Lazy = Lazy::new(|| { ) }); -/// Dynamic Algorithm Cache +/// Success based Dynamic Algorithm Cache pub static SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE: Lazy = Lazy::new(|| { Cache::new( "SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE", @@ -82,6 +79,16 @@ pub static SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE: Lazy = Lazy::new(|| { ) }); +/// Elimination based Dynamic Algorithm Cache +pub static ELIMINATION_BASED_DYNAMIC_ALGORITHM_CACHE: Lazy = Lazy::new(|| { + Cache::new( + "ELIMINATION_BASED_DYNAMIC_ALGORITHM_CACHE", + 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; @@ -102,6 +109,7 @@ pub enum CacheKind<'a> { Surcharge(Cow<'a, str>), CGraph(Cow<'a, str>), SuccessBasedDynamicRoutingCache(Cow<'a, str>), + EliminationBasedDynamicRoutingCache(Cow<'a, str>), PmFiltersCGraph(Cow<'a, str>), All(Cow<'a, str>), } @@ -117,7 +125,7 @@ impl<'a> TryFrom> for RedisValue { } } -impl<'a> TryFrom for CacheRedact<'a> { +impl TryFrom for CacheRedact<'_> { type Error = Report; fn try_from(v: RedisValue) -> Result { @@ -182,12 +190,11 @@ impl Cache { // Record the metrics of manual invalidation of cache entry by the application let eviction_listener = move |_, _, cause| { metrics::IN_MEMORY_CACHE_EVICTION_COUNT.add( - &metrics::CONTEXT, 1, - &add_attributes([ + router_env::metric_attributes!( ("cache_type", name.to_owned()), ("removal_cause", format!("{:?}", cause)), - ]), + ), ); }; let mut cache_builder = MokaCache::builder() @@ -214,17 +221,11 @@ impl Cache { // Add cache hit and cache miss metrics if val.is_some() { - metrics::IN_MEMORY_CACHE_HIT.add( - &metrics::CONTEXT, - 1, - &add_attributes([("cache_type", self.name)]), - ); + metrics::IN_MEMORY_CACHE_HIT + .add(1, router_env::metric_attributes!(("cache_type", self.name))); } else { - metrics::IN_MEMORY_CACHE_MISS.add( - &metrics::CONTEXT, - 1, - &add_attributes([("cache_type", self.name)]), - ); + metrics::IN_MEMORY_CACHE_MISS + .add(1, router_env::metric_attributes!(("cache_type", self.name))); } let val = (*val?).as_any().downcast_ref::().cloned(); @@ -258,10 +259,9 @@ impl Cache { pub async fn record_entry_count_metric(&self) { self.run_pending_tasks().await; - metrics::IN_MEMORY_CACHE_ENTRY_COUNT.observe( - &metrics::CONTEXT, + metrics::IN_MEMORY_CACHE_ENTRY_COUNT.record( self.get_entry_count(), - &add_attributes([("cache_type", self.name)]), + router_env::metric_attributes!(("cache_type", self.name)), ); } } diff --git a/crates/storage_impl/src/redis/kv_store.rs b/crates/storage_impl/src/redis/kv_store.rs index 74b1526fe8e8..9203a14ae21c 100644 --- a/crates/storage_impl/src/redis/kv_store.rs +++ b/crates/storage_impl/src/redis/kv_store.rs @@ -57,7 +57,7 @@ pub enum PartitionKey<'a> { }, } // PartitionKey::MerchantIdPaymentId {merchant_id, payment_id} -impl<'a> std::fmt::Display for PartitionKey<'a> { +impl std::fmt::Display for PartitionKey<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match *self { PartitionKey::MerchantIdPaymentId { @@ -257,19 +257,16 @@ where } }; + let attributes = router_env::metric_attributes!(("operation", operation.clone())); result .await .inspect(|_| { logger::debug!(kv_operation= %operation, status="success"); - let keyvalue = router_env::opentelemetry::KeyValue::new("operation", operation.clone()); - - metrics::KV_OPERATION_SUCCESSFUL.add(&metrics::CONTEXT, 1, &[keyvalue]); + metrics::KV_OPERATION_SUCCESSFUL.add(1, attributes); }) .inspect_err(|err| { logger::error!(kv_operation = %operation, status="error", error = ?err); - let keyvalue = router_env::opentelemetry::KeyValue::new("operation", operation); - - metrics::KV_OPERATION_FAILED.add(&metrics::CONTEXT, 1, &[keyvalue]); + metrics::KV_OPERATION_FAILED.add(1, attributes); }) } @@ -279,7 +276,7 @@ pub enum Op<'a> { Find, } -impl<'a> std::fmt::Display for Op<'a> { +impl std::fmt::Display for Op<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Op::Insert => f.write_str("insert"), @@ -312,11 +309,15 @@ where 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 + match Box::pin(kv_wrapper::( + store, + KvOperation::::HGet(field), + partition_key, + )) + .await { Ok(_) => { - metrics::KV_SOFT_KILL_ACTIVE_UPDATE.add(&metrics::CONTEXT, 1, &[]); + metrics::KV_SOFT_KILL_ACTIVE_UPDATE.add(1, &[]); MerchantStorageScheme::RedisKv } Err(_) => MerchantStorageScheme::PostgresOnly, diff --git a/crates/storage_impl/src/redis/pub_sub.rs b/crates/storage_impl/src/redis/pub_sub.rs index 6a2012f9b26f..42ad2ae0795a 100644 --- a/crates/storage_impl/src/redis/pub_sub.rs +++ b/crates/storage_impl/src/redis/pub_sub.rs @@ -6,8 +6,8 @@ use router_env::{logger, tracing::Instrument}; use crate::redis::cache::{ CacheKey, CacheKind, CacheRedact, ACCOUNTS_CACHE, CGRAPH_CACHE, CONFIG_CACHE, - DECISION_MANAGER_CACHE, PM_FILTERS_CGRAPH_CACHE, ROUTING_CACHE, - SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE, SURCHARGE_CACHE, + DECISION_MANAGER_CACHE, ELIMINATION_BASED_DYNAMIC_ALGORITHM_CACHE, PM_FILTERS_CGRAPH_CACHE, + ROUTING_CACHE, SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE, SURCHARGE_CACHE, }; #[async_trait::async_trait] @@ -138,6 +138,15 @@ impl PubSubInterface for std::sync::Arc { .await; key } + CacheKind::EliminationBasedDynamicRoutingCache(key) => { + ELIMINATION_BASED_DYNAMIC_ALGORITHM_CACHE + .remove(CacheKey { + key: key.to_string(), + prefix: message.tenant.clone(), + }) + .await; + key + } CacheKind::SuccessBasedDynamicRoutingCache(key) => { SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE .remove(CacheKey { @@ -205,6 +214,12 @@ impl PubSubInterface for std::sync::Arc { prefix: message.tenant.clone(), }) .await; + ELIMINATION_BASED_DYNAMIC_ALGORITHM_CACHE + .remove(CacheKey { + key: key.to_string(), + prefix: message.tenant.clone(), + }) + .await; ROUTING_CACHE .remove(CacheKey { key: key.to_string(), diff --git a/crates/storage_impl/src/utils.rs b/crates/storage_impl/src/utils.rs index b634f41a98f1..01e0e8cbc141 100644 --- a/crates/storage_impl/src/utils.rs +++ b/crates/storage_impl/src/utils.rs @@ -59,7 +59,7 @@ where Ok(output) => Ok(output), Err(redis_error) => match redis_error.current_context() { redis_interface::errors::RedisError::NotFound => { - metrics::KV_MISS.add(&metrics::CONTEXT, 1, &[]); + metrics::KV_MISS.add(1, &[]); database_call_closure().await } // Keeping the key empty here since the error would never go here. diff --git a/crates/test_utils/src/connector_auth.rs b/crates/test_utils/src/connector_auth.rs index a0fed3c003ac..7b6a1ba6b7a0 100644 --- a/crates/test_utils/src/connector_auth.rs +++ b/crates/test_utils/src/connector_auth.rs @@ -17,6 +17,7 @@ pub struct ConnectorAuthentication { #[cfg(feature = "payouts")] pub adyen_uk: Option, pub airwallex: Option, + pub amazonpay: Option, pub authorizedotnet: Option, pub bambora: Option, pub bamboraapac: Option, @@ -32,10 +33,12 @@ pub struct ConnectorAuthentication { pub cybersource: Option, pub datatrans: Option, pub deutschebank: Option, + pub digitalvirgo: Option, pub dlocal: Option, #[cfg(feature = "dummy_connector")] pub dummyconnector: Option, pub ebanx: Option, + pub elavon: Option, pub fiserv: Option, pub fiservemea: Option, pub fiuu: Option, @@ -46,13 +49,16 @@ pub struct ConnectorAuthentication { pub gpayments: Option, pub helcim: Option, pub iatapay: Option, + pub inespay: Option, pub itaubank: Option, + pub jpmorgan: Option, pub mifinity: Option, pub mollie: Option, pub multisafepay: Option, pub netcetera: Option, pub nexinets: Option, pub nexixpay: Option, + pub nomupay: Option, pub noon: Option, pub novalnet: Option, pub nmi: Option, @@ -71,6 +77,7 @@ pub struct ConnectorAuthentication { pub prophetpay: Option, pub rapyd: Option, pub razorpay: Option, + pub redsys: Option, pub shift4: Option, pub square: Option, pub stax: Option, @@ -82,11 +89,13 @@ pub struct ConnectorAuthentication { pub stripe_uk: Option, pub trustpay: Option, pub tsys: Option, + pub unified_authentication_service: Option, pub volt: Option, pub wellsfargo: Option, // pub wellsfargopayout: Option, pub wise: Option, pub worldpay: Option, + pub xendit: Option, pub worldline: Option, pub zen: Option, pub zsl: Option, diff --git a/crates/test_utils/tests/sample_auth.toml b/crates/test_utils/tests/sample_auth.toml index 08b24817c24e..c7ec4f12c544 100644 --- a/crates/test_utils/tests/sample_auth.toml +++ b/crates/test_utils/tests/sample_auth.toml @@ -30,6 +30,7 @@ api_key = "Bearer MyApiKey" [worldpay] api_key = "api_key" key1 = "key1" +api_secret = "Merchant Identifier" [payu] api_key = "Bearer MyApiKey" diff --git a/cypress-tests-v2/cypress/e2e/configs/Payment/Commons.js b/cypress-tests-v2/cypress/e2e/configs/Payment/Commons.js index ea7b4f09fd79..228dfd2cfdda 100644 --- a/cypress-tests-v2/cypress/e2e/configs/Payment/Commons.js +++ b/cypress-tests-v2/cypress/e2e/configs/Payment/Commons.js @@ -964,7 +964,11 @@ export const connectorDetails = { Response: { status: 400, body: { - error: "Json deserialize error: invalid card number length", + error: { + error_type: "invalid_request", + message: "Json deserialize error: invalid card number length", + code: "IR_06" + }, }, }, }, @@ -1068,8 +1072,11 @@ export const connectorDetails = { Response: { status: 400, body: { - error: - "Json deserialize error: unknown variant `United`, expected one of `AED`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BRL`, `BSD`, `BWP`, `BYN`, `BZD`, `CAD`, `CHF`, `CLP`, `CNY`, `COP`, `CRC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRU`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SEK`, `SGD`, `SHP`, `SLE`, `SLL`, `SOS`, `SRD`, `SSP`, `STN`, `SVC`, `SZL`, `THB`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYU`, `UZS`, `VES`, `VND`, `VUV`, `WST`, `XAF`, `XCD`, `XOF`, `XPF`, `YER`, `ZAR`, `ZMW`", + error: { + error_type: "invalid_request", + message: "Json deserialize error: unknown variant `United`, expected one of `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BRL`, `BSD`, `BTN`, `BWP`, `BYN`, `BZD`, `CAD`, `CDF`, `CHF`, `CLP`, `CNY`, `COP`, `CRC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRU`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLE`, `SLL`, `SOS`, `SRD`, `SSP`, `STN`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYU`, `UZS`, `VES`, `VND`, `VUV`, `WST`, `XAF`, `XCD`, `XOF`, `XPF`, `YER`, `ZAR`, `ZMW`, `ZWL`", + code: "IR_06" + }, }, }, }, @@ -1093,8 +1100,11 @@ export const connectorDetails = { Response: { status: 400, body: { - error: - "Json deserialize error: unknown variant `auto`, expected one of `automatic`, `manual`, `manual_multiple`, `scheduled`", + error: { + error_type: "invalid_request", + message: "Json deserialize error: unknown variant `auto`, expected one of `automatic`, `manual`, `manual_multiple`, `scheduled`", + code: "IR_06" + }, }, }, }, @@ -1117,8 +1127,11 @@ export const connectorDetails = { Response: { status: 400, body: { - error: - "Json deserialize error: unknown variant `this_supposed_to_be_a_card`, expected one of `card`, `card_redirect`, `pay_later`, `wallet`, `bank_redirect`, `bank_transfer`, `crypto`, `bank_debit`, `reward`, `real_time_payment`, `upi`, `voucher`, `gift_card`, `open_banking`", + error: { + error_type: "invalid_request", + message: "Json deserialize error: unknown variant `this_supposed_to_be_a_card`, expected one of `card`, `card_redirect`, `pay_later`, `wallet`, `bank_redirect`, `bank_transfer`, `crypto`, `bank_debit`, `reward`, `real_time_payment`, `upi`, `voucher`, `gift_card`, `open_banking`, `mobile_payment`", + code: "IR_06" + }, }, }, }, @@ -1186,7 +1199,7 @@ export const connectorDetails = { body: { error: { type: "invalid_request", - message: "A payment token or payment method data is required", + message: "A payment token or payment method data or ctp service details is required", code: "IR_06", }, }, diff --git a/cypress-tests-v2/cypress/e2e/configs/Payment/Utils.js b/cypress-tests-v2/cypress/e2e/configs/Payment/Utils.js index 6faea73d4ef9..569b557b6905 100644 --- a/cypress-tests-v2/cypress/e2e/configs/Payment/Utils.js +++ b/cypress-tests-v2/cypress/e2e/configs/Payment/Utils.js @@ -88,21 +88,14 @@ export function defaultErrorHandler(response, response_data) { if (typeof response.body.error === "object") { for (const key in response_data.body.error) { - expect(response_data.body.error[key]).to.equal(response.body.error[key]); + // Check if the error message is a Json deserialize error + let apiResponseContent = response.body.error[key]; + let expectedContent = response_data.body.error[key]; + if (typeof apiResponseContent === "string" && apiResponseContent.includes("Json deserialize error")) { + expect(apiResponseContent).to.include(expectedContent); + } else { + expect(apiResponseContent).to.equal(expectedContent); + } } - } else if (typeof response.body.error === "string") { - expect(response.body.error).to.include(response_data.body.error); } } - -export function isoTimeTomorrow() { - const now = new Date(); - - // Create a new date object for tomorrow - const tomorrow = new Date(now); - tomorrow.setDate(now.getDate() + 1); - - // Convert to ISO string format - const isoStringTomorrow = tomorrow.toISOString(); - return isoStringTomorrow; -} diff --git a/cypress-tests-v2/cypress/e2e/configs/Payment/_Reusable.js b/cypress-tests-v2/cypress/e2e/configs/Payment/_Reusable.js index b69506ff0755..353d96260b55 100644 --- a/cypress-tests-v2/cypress/e2e/configs/Payment/_Reusable.js +++ b/cypress-tests-v2/cypress/e2e/configs/Payment/_Reusable.js @@ -15,12 +15,23 @@ function normalise(input) { paybox: "Paybox", paypal: "Paypal", wellsfargo: "Wellsfargo", + fiuu: "Fiuu", // Add more known exceptions here }; if (typeof input !== "string") { - const spec_name = Cypress.spec.name.split("-")[1].split(".")[0]; - return `${spec_name}`; + const specName = Cypress.spec.name; + + if (specName.includes("-")) { + const parts = specName.split("-"); + + if (parts.length > 1 && parts[1].includes(".")) { + return parts[1].split(".")[0]; + } + } + + // Fallback + return `${specName}`; } const lowerCaseInput = input.toLowerCase(); diff --git a/cypress-tests-v2/cypress/e2e/spec/Payment/0001-[No3DS]Payments.cy.js b/cypress-tests-v2/cypress/e2e/spec/Payment/0001-[No3DS]Payments.cy.js new file mode 100644 index 000000000000..21766617c09a --- /dev/null +++ b/cypress-tests-v2/cypress/e2e/spec/Payment/0001-[No3DS]Payments.cy.js @@ -0,0 +1,132 @@ +/* +No 3DS Auto capture with Confirm True +No 3DS Auto capture with Confirm False +No 3DS Manual capture with Confirm True +No 3DS Manual capture with Confirm False +No 3DS Manual multiple capture with Confirm True +No 3DS Manual multiple capture with Confirm False +*/ + +import * as fixtures from "../../../fixtures/imports"; +import State from "../../../utils/State"; +import getConnectorDetails from "../../configs/Payment/Utils"; + +let globalState; + +// Below is an example of a test that is skipped just because it is not implemented yet +describe("[Payment] [No 3DS] [Payment Method: Card]", () => { + context("[Payment] [No 3DS] [Capture: Automatic] [Confirm: True]", () => { + let should_continue = true; + + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + beforeEach(function () { + if (!should_continue) { + this.skip(); + } + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + it.skip("Create payment intent", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ + "PaymentIntent" + ]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.paymentIntentCreateCall( + fixtures.createPaymentBody, + req_data, + res_data, + "no_three_ds", + "automatic", + globalState + ); + }); + + it.skip("List payment methods", () => { + cy.paymentMethodsListCall(globalState); + }); + + it.skip("Confirm payment intent", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ + "No3DSAutoCapture" + ]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.paymentIntentConfirmCall( + fixtures.confirmBody, + req_data, + res_data, + true, + globalState + ); + }); + + it.skip("Retrieve payment intent", () => { + cy.paymentIntentRetrieveCall(globalState); + }); + }); + context("[Payment] [No 3DS] [Capture: Automatic] [Confirm: False]", () => { + let should_continue = true; + + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + beforeEach(function () { + if (!should_continue) { + this.skip(); + } + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + it.skip("Create Payment Intent", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ + "PaymentIntent" + ]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.paymentIntentCreateCall( + fixtures.createPaymentBody, + req_data, + res_data, + "no_three_ds", + "automatic", + globalState + ); + }); + + it.skip("Payment Methods", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it.skip("Confirm No 3DS", () => { + let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ + "No3DSAutoCapture" + ]; + let req_data = data["Request"]; + let res_data = data["Response"]; + cy.paymentIntentConfirmCall( + fixtures.confirmBody, + req_data, + res_data, + true, + globalState + ); + }); + + it.skip("Retrieve payment intent", () => { + cy.paymentIntentRetrieveCall(globalState); + }); + }); +}); diff --git a/cypress-tests-v2/cypress/e2e/spec/Payment/0002-[3DS]Payments.cy.js b/cypress-tests-v2/cypress/e2e/spec/Payment/0002-[3DS]Payments.cy.js new file mode 100644 index 000000000000..2a1aa7d51528 --- /dev/null +++ b/cypress-tests-v2/cypress/e2e/spec/Payment/0002-[3DS]Payments.cy.js @@ -0,0 +1,8 @@ +/* +3DS Auto capture with Confirm True +3DS Auto capture with Confirm False +3DS Manual capture with Confirm True +3DS Manual capture with Confirm False +3DS Manual multiple capture with Confirm True +3DS Manual multiple capture with Confirm False +*/ diff --git a/cypress-tests-v2/cypress/fixtures/merchant_account.json b/cypress-tests-v2/cypress/fixtures/merchant_account.json index ac24e9565138..dbe88ae10d47 100644 --- a/cypress-tests-v2/cypress/fixtures/merchant_account.json +++ b/cypress-tests-v2/cypress/fixtures/merchant_account.json @@ -1,7 +1,6 @@ { "ma_create": { - "merchant_name": "Hyperswitch Seller", - "organization_id": "" + "merchant_name": "Hyperswitch Seller" }, "ma_update": { "merchant_name": "Hyperswitch" diff --git a/cypress-tests-v2/cypress/fixtures/organization.json b/cypress-tests-v2/cypress/fixtures/organization.json index 24d084ab606f..f0577db88765 100644 --- a/cypress-tests-v2/cypress/fixtures/organization.json +++ b/cypress-tests-v2/cypress/fixtures/organization.json @@ -1,6 +1,6 @@ { "org_create": { - "organization_name": "Hyperswitch Organization" + "organization_name": "Hyperswitch" }, "org_update": { "organization_name": "Hyperswitch", diff --git a/cypress-tests-v2/cypress/support/commands.js b/cypress-tests-v2/cypress/support/commands.js index c9d1ef3f24f5..b6955c542a70 100644 --- a/cypress-tests-v2/cypress/support/commands.js +++ b/cypress-tests-v2/cypress/support/commands.js @@ -26,10 +26,9 @@ // cy.task can only be used in support files (spec files or commands file) -import { - getValueByKey, - isoTimeTomorrow, -} from "../e2e/configs/Payment/Utils.js"; +import { nanoid } from "nanoid"; +import { getValueByKey } from "../e2e/configs/Payment/Utils.js"; +import { isoTimeTomorrow, validateEnv } from "../utils/RequestBodyUtils.js"; function logRequestId(xRequestId) { if (xRequestId) { @@ -48,6 +47,9 @@ Cypress.Commands.add( const base_url = globalState.get("baseUrl"); const url = `${base_url}/v2/organization`; + // Update request body + organizationCreateBody.organization_name += " " + nanoid(); + cy.request({ method: "POST", url: url, @@ -62,16 +64,16 @@ Cypress.Commands.add( if (response.status === 200) { expect(response.body) - .to.have.property("organization_id") + .to.have.property("id") .and.to.include("org_") .and.to.be.a("string").and.not.be.empty; - globalState.set("organizationId", response.body.organization_id); + globalState.set("organizationId", response.body.id); cy.task("setGlobalState", globalState.data); expect(response.body).to.have.property("metadata").and.to.equal(null); } else { // to be updated throw new Error( - `Organization create call failed with status ${response.status} and message ${response.body.message}` + `Organization create call failed with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -97,7 +99,7 @@ Cypress.Commands.add("organizationRetrieveCall", (globalState) => { if (response.status === 200) { expect(response.body) - .to.have.property("organization_id") + .to.have.property("id") .and.to.include("org_") .and.to.be.a("string").and.not.be.empty; expect(response.body.organization_name) @@ -105,13 +107,13 @@ Cypress.Commands.add("organizationRetrieveCall", (globalState) => { .and.to.be.a("string").and.not.be.empty; if (organization_id === undefined || organization_id === null) { - globalState.set("organizationId", response.body.organization_id); + globalState.set("organizationId", response.body.id); cy.task("setGlobalState", globalState.data); } } else { // to be updated throw new Error( - `Organization retrieve call failed with status ${response.status} and message ${response.body.message}` + `Organization retrieve call failed with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -125,6 +127,9 @@ Cypress.Commands.add( const organization_id = globalState.get("organizationId"); const url = `${base_url}/v2/organization/${organization_id}`; + // Update request body + organizationUpdateBody.organization_name += " " + nanoid(); + cy.request({ method: "PUT", url: url, @@ -139,20 +144,20 @@ Cypress.Commands.add( if (response.status === 200) { expect(response.body) - .to.have.property("organization_id") + .to.have.property("id") .and.to.include("org_") .and.to.be.a("string").and.not.be.empty; expect(response.body).to.have.property("metadata").and.to.be.a("object") .and.not.be.empty; if (organization_id === undefined || organization_id === null) { - globalState.set("organizationId", response.body.organization_id); + globalState.set("organizationId", response.body.id); cy.task("setGlobalState", globalState.data); } } else { // to be updated throw new Error( - `Organization update call failed with status ${response.status} and message ${response.body.message}` + `Organization update call failed with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -166,22 +171,22 @@ Cypress.Commands.add( // Define the necessary variables and constants const api_key = globalState.get("adminApiKey"); const base_url = globalState.get("baseUrl"); + const key_id_type = "publishable_key"; + const key_id = validateEnv(base_url, key_id_type); const organization_id = globalState.get("organizationId"); - const url = `${base_url}/v2/merchant_accounts`; + const url = `${base_url}/v2/merchant-accounts`; const merchant_name = merchantAccountCreateBody.merchant_name .replaceAll(" ", "") .toLowerCase(); - // Update request body - merchantAccountCreateBody.organization_id = organization_id; - cy.request({ method: "POST", url: url, headers: { "Content-Type": "application/json", "api-key": api_key, + "X-Organization-Id": organization_id, }, body: merchantAccountCreateBody, failOnStatusCode: false, @@ -194,14 +199,9 @@ Cypress.Commands.add( .and.to.include(`${merchant_name}_`) .and.to.be.a("string").and.not.be.empty; - if (base_url.includes("sandbox") || base_url.includes("integ")) - expect(response.body) - .to.have.property("publishable_key") - .and.to.include("pk_snd").and.to.not.be.empty; - else if (base_url.includes("localhost")) - expect(response.body) - .to.have.property("publishable_key") - .and.to.include("pk_dev").and.to.not.be.empty; + expect(response.body) + .to.have.property(key_id_type) + .and.to.include(key_id).and.to.not.be.empty; globalState.set("merchantId", response.body.id); globalState.set("publishableKey", response.body.publishable_key); @@ -210,7 +210,7 @@ Cypress.Commands.add( } else { // to be updated throw new Error( - `Merchant create call failed with status ${response.status} and message ${response.body.message}` + `Merchant create call failed with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -220,8 +220,10 @@ Cypress.Commands.add("merchantAccountRetrieveCall", (globalState) => { // Define the necessary variables and constants const api_key = globalState.get("adminApiKey"); const base_url = globalState.get("baseUrl"); + const key_id_type = "publishable_key"; + const key_id = validateEnv(base_url, key_id_type); const merchant_id = globalState.get("merchantId"); - const url = `${base_url}/v2/merchant_accounts/${merchant_id}`; + const url = `${base_url}/v2/merchant-accounts/${merchant_id}`; cy.request({ method: "GET", @@ -238,14 +240,8 @@ Cypress.Commands.add("merchantAccountRetrieveCall", (globalState) => { expect(response.body).to.have.property("id").and.to.be.a("string").and.not .be.empty; - if (base_url.includes("sandbox") || base_url.includes("integ")) - expect(response.body) - .to.have.property("publishable_key") - .and.to.include("pk_snd").and.to.not.be.empty; - else - expect(response.body) - .to.have.property("publishable_key") - .and.to.include("pk_dev").and.to.not.be.empty; + expect(response.body).to.have.property(key_id_type).and.to.include(key_id) + .and.to.not.be.empty; if (merchant_id === undefined || merchant_id === null) { globalState.set("merchantId", response.body.id); @@ -255,7 +251,7 @@ Cypress.Commands.add("merchantAccountRetrieveCall", (globalState) => { } else { // to be updated throw new Error( - `Merchant account retrieve call failed with status ${response.status} and message ${response.body.message}` + `Merchant account retrieve call failed with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -266,8 +262,10 @@ Cypress.Commands.add( // Define the necessary variables and constants const api_key = globalState.get("adminApiKey"); const base_url = globalState.get("baseUrl"); + const key_id_type = "publishable_key"; + const key_id = validateEnv(base_url, key_id_type); const merchant_id = globalState.get("merchantId"); - const url = `${base_url}/v2/merchant_accounts/${merchant_id}`; + const url = `${base_url}/v2/merchant-accounts/${merchant_id}`; const merchant_name = merchantAccountUpdateBody.merchant_name; @@ -286,14 +284,10 @@ Cypress.Commands.add( if (response.status === 200) { expect(response.body.id).to.equal(merchant_id); - if (base_url.includes("sandbox") || base_url.includes("integ")) - expect(response.body) - .to.have.property("publishable_key") - .and.to.include("pk_snd").and.to.not.be.empty; - else - expect(response.body) - .to.have.property("publishable_key") - .and.to.include("pk_dev").and.to.not.be.empty; + expect(response.body) + .to.have.property(key_id_type) + .and.to.include(key_id).and.to.not.be.empty; + expect(response.body.merchant_name).to.equal(merchant_name); if (merchant_id === undefined || merchant_id === null) { @@ -304,7 +298,7 @@ Cypress.Commands.add( } else { // to be updated throw new Error( - `Merchant account update call failed with status ${response.status} and message ${response.body.message}` + `Merchant account update call failed with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -351,7 +345,7 @@ Cypress.Commands.add( } else { // to be updated throw new Error( - `Business profile create call failed with status ${response.status} and message ${response.body.message}` + `Business profile create call failed with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -392,7 +386,7 @@ Cypress.Commands.add("businessProfileRetrieveCall", (globalState) => { } else { // to be updated throw new Error( - `Business profile retrieve call failed with status ${response.status} and message ${response.body.message}` + `Business profile retrieve call failed with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -438,7 +432,7 @@ Cypress.Commands.add( } else { // to be updated throw new Error( - `Business profile update call failed with status ${response.status} and message ${response.body.message}` + `Business profile update call failed with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -462,7 +456,7 @@ Cypress.Commands.add( const base_url = globalState.get("baseUrl"); const merchant_id = globalState.get("merchantId"); const profile_id = globalState.get("profileId"); - const url = `${base_url}/v2/connector_accounts`; + const url = `${base_url}/v2/connector-accounts`; const customHeaders = { "x-merchant-id": merchant_id, @@ -496,8 +490,8 @@ Cypress.Commands.add( authDetails.connector_account_details; if (authDetails && authDetails.metadata) { - createConnectorBody.metadata = { - ...createConnectorBody.metadata, // Preserve existing metadata fields + mcaCreateBody.metadata = { + ...mcaCreateBody.metadata, // Preserve existing metadata fields ...authDetails.metadata, // Merge with authDetails.metadata }; } @@ -527,7 +521,7 @@ Cypress.Commands.add( } else { // to be updated throw new Error( - `Merchant connector account create call failed with status ${response.status} and message ${response.body.message}` + `Merchant connector account create call failed with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -542,7 +536,7 @@ Cypress.Commands.add("mcaRetrieveCall", (globalState) => { const connector_name = globalState.get("connectorId"); const merchant_connector_id = globalState.get("merchantConnectorId"); const merchant_id = globalState.get("merchantId"); - const url = `${base_url}/v2/connector_accounts/${merchant_connector_id}`; + const url = `${base_url}/v2/connector-accounts/${merchant_connector_id}`; const customHeaders = { "x-merchant-id": merchant_id, @@ -575,7 +569,7 @@ Cypress.Commands.add("mcaRetrieveCall", (globalState) => { } else { // to be updated throw new Error( - `Merchant connector account retrieve call failed with status ${response.status} and message ${response.body.message}` + `Merchant connector account retrieve call failed with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -596,7 +590,7 @@ Cypress.Commands.add( const merchant_connector_id = globalState.get("merchantConnectorId"); const merchant_id = globalState.get("merchantId"); const profile_id = globalState.get("profileId"); - const url = `${base_url}/v2/connector_accounts/${merchant_connector_id}`; + const url = `${base_url}/v2/connector-accounts/${merchant_connector_id}`; const customHeaders = { "x-merchant-id": merchant_id, @@ -640,7 +634,7 @@ Cypress.Commands.add( } else { // to be updated throw new Error( - `Merchant connector account update call failed with status ${response.status} and message ${response.body.message}` + `Merchant connector account update call failed with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -656,8 +650,10 @@ Cypress.Commands.add("apiKeyCreateCall", (apiKeyCreateBody, globalState) => { // We do not want to keep API Key forever, // so we set the expiry to tomorrow as new merchant accounts are created with every run const expiry = isoTimeTomorrow(); + const key_id_type = "key_id"; + const key_id = validateEnv(base_url, key_id_type); const merchant_id = globalState.get("merchantId"); - const url = `${base_url}/v2/api_keys`; + const url = `${base_url}/v2/api-keys`; const customHeaders = { "x-merchant-id": merchant_id, @@ -684,13 +680,8 @@ Cypress.Commands.add("apiKeyCreateCall", (apiKeyCreateBody, globalState) => { expect(response.body.description).to.equal(apiKeyCreateBody.description); // API Key assertions are intentionally excluded to avoid being exposed in the logs - if (base_url.includes("sandbox") || base_url.includes("integ")) { - expect(response.body).to.have.property("key_id").and.to.include("snd_") - .and.to.not.be.empty; - } else if (base_url.includes("localhost")) { - expect(response.body).to.have.property("key_id").and.to.include("dev_") - .and.to.not.be.empty; - } + expect(response.body).to.have.property(key_id_type).and.to.include(key_id) + .and.to.not.be.empty; globalState.set("apiKeyId", response.body.key_id); globalState.set("apiKey", response.body.api_key); @@ -699,7 +690,7 @@ Cypress.Commands.add("apiKeyCreateCall", (apiKeyCreateBody, globalState) => { } else { // to be updated throw new Error( - `API Key create call failed with status ${response.status} and message ${response.body.message}` + `API Key create call failed with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -708,9 +699,11 @@ Cypress.Commands.add("apiKeyRetrieveCall", (globalState) => { // Define the necessary variables and constant const api_key = globalState.get("adminApiKey"); const base_url = globalState.get("baseUrl"); + const key_id_type = "key_id"; + const key_id = validateEnv(base_url, key_id_type); const merchant_id = globalState.get("merchantId"); const api_key_id = globalState.get("apiKeyId"); - const url = `${base_url}/v2/api_keys/${api_key_id}`; + const url = `${base_url}/v2/api-keys/${api_key_id}`; const customHeaders = { "x-merchant-id": merchant_id, @@ -730,15 +723,9 @@ Cypress.Commands.add("apiKeyRetrieveCall", (globalState) => { if (response.status === 200) { expect(response.body.merchant_id).to.equal(merchant_id); - // API Key assertions are intentionally excluded to avoid being exposed in the logs - if (base_url.includes("sandbox") || base_url.includes("integ")) { - expect(response.body).to.have.property("key_id").and.to.include("snd_") - .and.to.not.be.empty; - } else if (base_url.includes("localhost")) { - expect(response.body).to.have.property("key_id").and.to.include("dev_") - .and.to.not.be.empty; - } + expect(response.body).to.have.property(key_id_type).and.to.include(key_id) + .and.to.not.be.empty; if (api_key === undefined || api_key === null) { globalState.set("apiKey", response.body.api_key); @@ -747,7 +734,7 @@ Cypress.Commands.add("apiKeyRetrieveCall", (globalState) => { } else { // to be updated throw new Error( - `API Key retrieve call failed with status ${response.status} and message ${response.body.message}` + `API Key retrieve call failed with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -760,8 +747,10 @@ Cypress.Commands.add("apiKeyUpdateCall", (apiKeyUpdateBody, globalState) => { // We do not want to keep API Key forever, // so we set the expiry to tomorrow as new merchant accounts are created with every run const expiry = isoTimeTomorrow(); + const key_id_type = "key_id"; + const key_id = validateEnv(base_url, key_id_type); const merchant_id = globalState.get("merchantId"); - const url = `${base_url}/v2/api_keys/${api_key_id}`; + const url = `${base_url}/v2/api-keys/${api_key_id}`; const customHeaders = { "x-merchant-id": merchant_id, @@ -788,13 +777,8 @@ Cypress.Commands.add("apiKeyUpdateCall", (apiKeyUpdateBody, globalState) => { expect(response.body.description).to.equal(apiKeyUpdateBody.description); // API Key assertions are intentionally excluded to avoid being exposed in the logs - if (base_url.includes("sandbox") || base_url.includes("integ")) { - expect(response.body).to.have.property("key_id").and.to.include("snd_") - .and.to.not.be.empty; - } else if (base_url.includes("localhost")) { - expect(response.body).to.have.property("key_id").and.to.include("dev_") - .and.to.not.be.empty; - } + expect(response.body).to.have.property(key_id_type).and.to.include(key_id) + .and.to.not.be.empty; if (api_key === undefined || api_key === null) { globalState.set("apiKey", response.body.api_key); @@ -803,7 +787,7 @@ Cypress.Commands.add("apiKeyUpdateCall", (apiKeyUpdateBody, globalState) => { } else { // to be updated throw new Error( - `API Key update call failed with status ${response.status} and message ${response.body.message}` + `API Key update call failed with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -817,7 +801,7 @@ Cypress.Commands.add( const api_key = globalState.get("userInfoToken"); const base_url = globalState.get("baseUrl"); const profile_id = globalState.get("profileId"); - const url = `${base_url}/v2/routing_algorithm`; + const url = `${base_url}/v2/routing-algorithm`; // Update request body routingSetupBody.algorithm.data = payload.data; @@ -849,7 +833,7 @@ Cypress.Commands.add( } else { // to be updated throw new Error( - `Routing algorithm setup call failed with status ${response.status} and message ${response.body.message}` + `Routing algorithm setup call failed with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -863,7 +847,7 @@ Cypress.Commands.add( const base_url = globalState.get("baseUrl"); const profile_id = globalState.get("profileId"); const routing_algorithm_id = globalState.get("routingAlgorithmId"); - const url = `${base_url}/v2/profiles/${profile_id}/activate_routing_algorithm`; + const url = `${base_url}/v2/profiles/${profile_id}/activate-routing-algorithm`; // Update request body routingActivationBody.routing_algorithm_id = routing_algorithm_id; @@ -888,7 +872,7 @@ Cypress.Commands.add( } else { // to be updated throw new Error( - `Routing algorithm activation call failed with status ${response.status} and message ${response.body.message}` + `Routing algorithm activation call failed with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -901,7 +885,7 @@ Cypress.Commands.add("routingActivationRetrieveCall", (globalState) => { const profile_id = globalState.get("profileId"); const query_params = "limit=10"; const routing_algorithm_id = globalState.get("routingAlgorithmId"); - const url = `${base_url}/v2/profiles/${profile_id}/routing_algorithm?${query_params}`; + const url = `${base_url}/v2/profiles/${profile_id}/routing-algorithm?${query_params}`; cy.request({ method: "GET", @@ -927,7 +911,7 @@ Cypress.Commands.add("routingActivationRetrieveCall", (globalState) => { } else { // to be updated throw new Error( - `Routing algorithm activation retrieve call failed with status ${response.status} and message ${response.body.message}` + `Routing algorithm activation retrieve call failed with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -938,7 +922,7 @@ Cypress.Commands.add("routingDeactivateCall", (globalState) => { const base_url = globalState.get("baseUrl"); const profile_id = globalState.get("profileId"); const routing_algorithm_id = globalState.get("routingAlgorithmId"); - const url = `${base_url}/v2/profiles/${profile_id}/deactivate_routing_algorithm`; + const url = `${base_url}/v2/profiles/${profile_id}/deactivate-routing-algorithm`; cy.request({ method: "PATCH", @@ -962,7 +946,7 @@ Cypress.Commands.add("routingDeactivateCall", (globalState) => { } else { // to be updated throw new Error( - `Routing algorithm deactivation call failed with status ${response.status} and message ${response.body.message}` + `Routing algorithm deactivation call failed with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -973,7 +957,7 @@ Cypress.Commands.add("routingRetrieveCall", (globalState) => { const base_url = globalState.get("baseUrl"); const profile_id = globalState.get("profileId"); const routing_algorithm_id = globalState.get("routingAlgorithmId"); - const url = `${base_url}/v2/routing_algorithm/${routing_algorithm_id}`; + const url = `${base_url}/v2/routing-algorithm/${routing_algorithm_id}`; cy.request({ method: "GET", @@ -999,7 +983,7 @@ Cypress.Commands.add("routingRetrieveCall", (globalState) => { } else { // to be updated throw new Error( - `Routing algorithm activation retrieve call failed with status ${response.status} and message ${response.body.message}` + `Routing algorithm activation retrieve call failed with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -1012,13 +996,13 @@ Cypress.Commands.add( const base_url = globalState.get("baseUrl"); const profile_id = globalState.get("profileId"); const routing_algorithm_id = globalState.get("routingAlgorithmId"); - const url = `${base_url}/v2/profiles/${profile_id}/fallback_routing`; + const url = `${base_url}/v2/profiles/${profile_id}/fallback-routing`; // Update request body routingDefaultFallbackBody = payload; cy.request({ - method: "POST", + method: "PATCH", url: url, headers: { Authorization: `Bearer ${api_key}`, @@ -1034,7 +1018,7 @@ Cypress.Commands.add( } else { // to be updated throw new Error( - `Routing algorithm activation retrieve call failed with status ${response.status} and message ${response.body.message}` + `Routing algorithm activation retrieve call failed with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -1045,7 +1029,7 @@ Cypress.Commands.add("routingFallbackRetrieveCall", (globalState) => { const api_key = globalState.get("userInfoToken"); const base_url = globalState.get("baseUrl"); const profile_id = globalState.get("profileId"); - const url = `${base_url}/v2/profiles/${profile_id}/fallback_routing`; + const url = `${base_url}/v2/profiles/${profile_id}/fallback-routing`; cy.request({ method: "GET", @@ -1063,7 +1047,7 @@ Cypress.Commands.add("routingFallbackRetrieveCall", (globalState) => { } else { // to be updated throw new Error( - `Routing algorithm activation retrieve call failed with status ${response.status} and message ${response.body.message}` + `Routing algorithm activation retrieve call failed with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -1102,7 +1086,7 @@ Cypress.Commands.add("userLogin", (globalState) => { } else { // to be updated throw new Error( - `User login call failed to get totp token with status ${response.status} and message ${response.body.message}` + `User login call failed to get totp token with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -1135,7 +1119,7 @@ Cypress.Commands.add("terminate2Fa", (globalState) => { } else { // to be updated throw new Error( - `2FA terminate call failed with status ${response.status} and message ${response.body.message}` + `2FA terminate call failed with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -1168,7 +1152,7 @@ Cypress.Commands.add("userInfo", (globalState) => { } else { // to be updated throw new Error( - `User login call failed to fetch user info with status ${response.status} and message ${response.body.message}` + `User login call failed to fetch user info with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -1179,8 +1163,10 @@ Cypress.Commands.add("merchantAccountsListCall", (globalState) => { // Define the necessary variables and constants const api_key = globalState.get("adminApiKey"); const base_url = globalState.get("baseUrl"); + const key_id_type = "publishable_key"; + const key_id = validateEnv(base_url, key_id_type); const organization_id = globalState.get("organizationId"); - const url = `${base_url}/v2/organization/${organization_id}/merchant_accounts`; + const url = `${base_url}/v2/organization/${organization_id}/merchant-accounts`; cy.request({ method: "GET", @@ -1200,21 +1186,15 @@ Cypress.Commands.add("merchantAccountsListCall", (globalState) => { expect(response.body[key]) .to.have.property("organization_id") .and.to.equal(organization_id); - if (base_url.includes("integ") || base_url.includes("sandbox")) { - expect(response.body[key]) - .to.have.property("publishable_key") - .and.include("pk_snd_").and.to.not.be.empty; - } else if (base_url.includes("localhost")) { - expect(response.body[key]) - .to.have.property("publishable_key") - .and.include("pk_dev_").and.to.not.be.empty; - } + expect(response.body[key]) + .to.have.property(key_id_type) + .and.include(key_id).and.to.not.be.empty; expect(response.body[key]).to.have.property("id").and.to.not.be.empty; } } else { // to be updated throw new Error( - `Merchant accounts list call failed with status ${response.status} and message ${response.body.message}` + `Merchant accounts list call failed with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -1224,7 +1204,7 @@ Cypress.Commands.add("businessProfilesListCall", (globalState) => { const api_key = globalState.get("adminApiKey"); const base_url = globalState.get("baseUrl"); const merchant_id = globalState.get("merchantId"); - const url = `${base_url}/v2/merchant_accounts/${merchant_id}/profiles`; + const url = `${base_url}/v2/merchant-accounts/${merchant_id}/profiles`; const customHeaders = { "x-merchant-id": merchant_id, @@ -1255,7 +1235,7 @@ Cypress.Commands.add("businessProfilesListCall", (globalState) => { } else { // to be updated throw new Error( - `Business profiles list call failed with status ${response.status} and message ${response.body.message}` + `Business profiles list call failed with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -1266,7 +1246,7 @@ Cypress.Commands.add("mcaListCall", (globalState, service_type) => { const base_url = globalState.get("baseUrl"); const merchant_id = globalState.get("merchantId"); const profile_id = globalState.get("profileId"); - const url = `${base_url}/v2/profiles/${profile_id}/connector_accounts`; + const url = `${base_url}/v2/profiles/${profile_id}/connector-accounts`; const customHeaders = { "x-merchant-id": merchant_id, @@ -1316,7 +1296,7 @@ Cypress.Commands.add("mcaListCall", (globalState, service_type) => { } else { // to be updated throw new Error( - `Merchant connector account list call failed with status ${response.status} and message ${response.body.message}` + `Merchant connector account list call failed with status ${response.status} and message: "${response.body.error.message}"` ); } }); @@ -1325,8 +1305,10 @@ Cypress.Commands.add("apiKeysListCall", (globalState) => { // Define the necessary variables and constants const api_key = globalState.get("adminApiKey"); const base_url = globalState.get("baseUrl"); + const key_id_type = "key_id"; + const key_id = validateEnv(base_url, key_id_type); const merchant_id = globalState.get("merchantId"); - const url = `${base_url}/v2/api_keys/list`; + const url = `${base_url}/v2/api-keys/list`; const customHeaders = { "x-merchant-id": merchant_id, @@ -1349,8 +1331,8 @@ Cypress.Commands.add("apiKeysListCall", (globalState) => { expect(response.body).to.be.an("array").and.to.not.be.empty; for (const key in response.body) { expect(response.body[key]) - .to.have.property("key_id") - .and.to.include("dev_").and.to.not.be.empty; + .to.have.property(key_id_type) + .and.to.include(key_id).and.to.not.be.empty; expect(response.body[key]) .to.have.property("merchant_id") .and.to.equal(merchant_id).and.to.not.be.empty; @@ -1358,13 +1340,74 @@ Cypress.Commands.add("apiKeysListCall", (globalState) => { } else { // to be updated throw new Error( - `API Keys list call failed with status ${response.status} and message ${response.body.message}` + `API Keys list call failed with status ${response.status} and message: "${response.body.error.message}"` ); } }); }); -// templates +// Payment API calls +// Update the below commands while following the conventions +// Below is an example of how the payment intent create call should look like (update the below command as per the need) +Cypress.Commands.add( + "paymentIntentCreateCall", + ( + globalState, + paymentRequestBody, + paymentResponseBody + /* Add more variables based on the need*/ + ) => { + // Define the necessary variables and constants at the top + // Also construct the URL here + const api_key = globalState.get("apiKey"); + const base_url = globalState.get("baseUrl"); + const profile_id = globalState.get("profileId"); + const url = `${base_url}/v2/payments/create-intent`; + + // Update request body if needed + paymentRequestBody = {}; + + // Pass Custom Headers + const customHeaders = { + "x-profile-id": profile_id, + }; + + cy.request({ + method: "POST", + url: url, + headers: { + "api-key": api_key, + "Content-Type": "application/json", + ...customHeaders, + }, + body: paymentRequestBody, + failOnStatusCode: false, + }).then((response) => { + // Logging x-request-id is mandatory + logRequestId(response.headers["x-request-id"]); + + if (response.status === 200) { + // Update the assertions based on the need + expect(response.body).to.deep.equal(paymentResponseBody); + } else if (response.status === 400) { + // Add 4xx validations here + expect(response.body).to.deep.equal(paymentResponseBody); + } else if (response.status === 500) { + // Add 5xx validations here + expect(response.body).to.deep.equal(paymentResponseBody); + } else { + // If status code is other than the ones mentioned above, default should be thrown + throw new Error( + `Payment intent create call failed with status ${response.status} and message: "${response.body.error.message}"` + ); + } + }); + } +); +Cypress.Commands.add("paymentIntentConfirmCall", (globalState) => {}); +Cypress.Commands.add("paymentIntentRetrieveCall", (globalState) => {}); + +// templates for future use Cypress.Commands.add("", () => { cy.request({}).then((response) => {}); }); diff --git a/cypress-tests-v2/cypress/utils/RequestBodyUtils.js b/cypress-tests-v2/cypress/utils/RequestBodyUtils.js new file mode 100644 index 000000000000..0926cbd97bfb --- /dev/null +++ b/cypress-tests-v2/cypress/utils/RequestBodyUtils.js @@ -0,0 +1,48 @@ +const keyPrefixes = { + localhost: { + publishable_key: "pk_dev_", + key_id: "dev_", + }, + integ: { + publishable_key: "pk_snd_", + key_id: "snd_", + }, + sandbox: { + publishable_key: "pk_snd_", + key_id: "snd_", + }, +}; + +export function isoTimeTomorrow() { + const now = new Date(); + + // Create a new date object for tomorrow + const tomorrow = new Date(now); + tomorrow.setDate(now.getDate() + 1); + + // Convert to ISO string format + const isoStringTomorrow = tomorrow.toISOString(); + return isoStringTomorrow; +} + +export function validateEnv(baseUrl, keyIdType) { + if (!baseUrl) { + throw new Error("Please provide a baseUrl"); + } + + const environment = Object.keys(keyPrefixes).find((env) => + baseUrl.includes(env) + ); + + if (!environment) { + throw new Error("Unsupported baseUrl"); + } + + const prefix = keyPrefixes[environment][keyIdType]; + + if (!prefix) { + throw new Error(`Unsupported keyIdType: ${keyIdType}`); + } + + return prefix; +} diff --git a/cypress-tests-v2/package-lock.json b/cypress-tests-v2/package-lock.json index d9282b8d3ce9..cac88cab055b 100644 --- a/cypress-tests-v2/package-lock.json +++ b/cypress-tests-v2/package-lock.json @@ -10,9 +10,10 @@ "license": "ISC", "devDependencies": { "@types/fs-extra": "^11.0.4", - "cypress": "^13.14.2", + "cypress": "^13.16.0", "cypress-mochawesome-reporter": "^3.8.2", "jsqr": "^1.4.0", + "nanoid": "^5.0.8", "prettier": "^3.3.2" } }, @@ -28,9 +29,9 @@ } }, "node_modules/@cypress/request": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.5.tgz", - "integrity": "sha512-v+XHd9XmWbufxF1/bTaVm2yhbxY+TB4YtWRqF2zaXBlDNMkls34KiATz0AVDLavL3iB6bQk9/7n3oY1EoLSWGA==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.6.tgz", + "integrity": "sha512-fi0eVdCOtKu5Ed6+E8mYxUF6ZTFJDZvHogCBelM0xVXmrDEkyM22gRArQzq1YcHPm1V47Vf/iAD+WgVdUlJCGg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -49,7 +50,7 @@ "performance-now": "^2.1.0", "qs": "6.13.0", "safe-buffer": "^5.1.2", - "tough-cookie": "^4.1.3", + "tough-cookie": "^5.0.0", "tunnel-agent": "^0.6.0", "uuid": "^8.3.2" }, @@ -567,9 +568,9 @@ } }, "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.0.0.tgz", + "integrity": "sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==", "dev": true, "funding": [ { @@ -726,8 +727,8 @@ "license": "MIT" }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "license": "MIT", @@ -741,14 +742,14 @@ } }, "node_modules/cypress": { - "version": "13.14.2", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.14.2.tgz", - "integrity": "sha512-lsiQrN17vHMB2fnvxIrKLAjOr9bPwsNbPZNrWf99s4u+DVmCY6U+w7O3GGG9FvP4EUVYaDu+guWeNLiUzBrqvA==", + "version": "13.16.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.16.0.tgz", + "integrity": "sha512-g6XcwqnvzXrqiBQR/5gN+QsyRmKRhls1y5E42fyOvsmU7JuY+wM6uHJWj4ZPttjabzbnRvxcik2WemR8+xT6FA==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { - "@cypress/request": "^3.0.1", + "@cypress/request": "^3.0.6", "@cypress/xvfb": "^1.2.4", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", @@ -759,6 +760,7 @@ "cachedir": "^2.3.0", "chalk": "^4.1.0", "check-more-types": "^2.24.0", + "ci-info": "^4.0.0", "cli-cursor": "^3.1.0", "cli-table3": "~0.6.1", "commander": "^6.2.1", @@ -773,7 +775,6 @@ "figures": "^3.2.0", "fs-extra": "^9.1.0", "getos": "^3.2.1", - "is-ci": "^3.0.1", "is-installed-globally": "~0.4.0", "lazy-ass": "^1.6.0", "listr2": "^3.8.3", @@ -788,6 +789,7 @@ "semver": "^7.5.3", "supports-color": "^8.1.1", "tmp": "~0.2.3", + "tree-kill": "1.2.2", "untildify": "^4.0.0", "yauzl": "^2.10.0" }, @@ -1055,7 +1057,7 @@ "dev": true, "license": "MIT", "dependencies": { - "cross-spawn": "^7.0.0", + "cross-spawn": "^7.0.6", "get-stream": "^5.0.0", "human-signals": "^1.1.1", "is-stream": "^2.0.0", @@ -1203,9 +1205,9 @@ } }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", "dev": true, "license": "MIT", "dependencies": { @@ -1583,19 +1585,6 @@ "node": ">=8" } }, - "node_modules/is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ci-info": "^3.2.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2457,6 +2446,25 @@ "dev": true, "license": "MIT" }, + "node_modules/nanoid": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.8.tgz", + "integrity": "sha512-TcJPw+9RV9dibz1hHUzlLVy8N4X9TnwirAjrU08Juo6BNKggzVfP2ZJ/3ZUSq15Xl5i85i+Z89XBO90pB2PghQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -2733,13 +2741,6 @@ "dev": true, "license": "MIT" }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true, - "license": "MIT" - }, "node_modules/pump": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", @@ -2751,16 +2752,6 @@ "once": "^1.3.1" } }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -2777,13 +2768,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true, - "license": "MIT" - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -2843,13 +2827,6 @@ "dev": true, "license": "ISC" }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true, - "license": "MIT" - }, "node_modules/restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -3150,6 +3127,26 @@ "dev": true, "license": "MIT" }, + "node_modules/tldts": { + "version": "6.1.59", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.59.tgz", + "integrity": "sha512-472ilPxsRuqBBpn+KuRBHJvZhk6tTo4yTVsmODrLBNLwRYJPkDfMEHivgNwp5iEl+cbrZzzRtLKRxZs7+QKkRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.59" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.59", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.59.tgz", + "integrity": "sha512-EiYgNf275AQyVORl8HQYYe7rTVnmLb4hkWK7wAk/12Ksy5EiHpmUmTICa4GojookBPC8qkLMBKKwCmzNA47ZPQ==", + "dev": true, + "license": "MIT" + }, "node_modules/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", @@ -3175,29 +3172,26 @@ } }, "node_modules/tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", + "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" + "tldts": "^6.1.32" }, "engines": { - "node": ">=6" + "node": ">=16" } }, - "node_modules/tough-cookie/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 4.0.0" + "bin": { + "tree-kill": "cli.js" } }, "node_modules/tslib": { @@ -3267,17 +3261,6 @@ "node": ">=8" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", diff --git a/cypress-tests-v2/package.json b/cypress-tests-v2/package.json index e06c1a63be20..37a51db93cbe 100644 --- a/cypress-tests-v2/package.json +++ b/cypress-tests-v2/package.json @@ -15,9 +15,10 @@ "license": "ISC", "devDependencies": { "@types/fs-extra": "^11.0.4", - "cypress": "^13.14.2", + "cypress": "^13.16.0", "cypress-mochawesome-reporter": "^3.8.2", "jsqr": "^1.4.0", - "prettier": "^3.3.2" + "prettier": "^3.3.2", + "nanoid": "^5.0.8" } } diff --git a/cypress-tests/.gitignore b/cypress-tests/.gitignore index 69b344a1d968..663fbaa3f4df 100644 --- a/cypress-tests/.gitignore +++ b/cypress-tests/.gitignore @@ -1,3 +1,4 @@ +.DS_Store creds.json reports run_all.sh diff --git a/cypress-tests/.prettierrc b/cypress-tests/.prettierrc new file mode 100644 index 000000000000..729684105779 --- /dev/null +++ b/cypress-tests/.prettierrc @@ -0,0 +1,9 @@ +{ + "bracketSpacing": true, + "endOfLine": "auto", + "printWidth": 80, + "semi": true, + "singleQuote": false, + "tabWidth": 2, + "trailingComma": "es5" +} diff --git a/cypress-tests/.prettierrc.json b/cypress-tests/.prettierrc.json deleted file mode 100644 index 479993c13346..000000000000 --- a/cypress-tests/.prettierrc.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "trailingComma": "es5", - "tabWidth": 2, - "semi": true, - "singleQuote": false, - "printWidth": 120 -} diff --git a/cypress-tests/cypress.config.js b/cypress-tests/cypress.config.js index 8e7eb77fe2a9..a501f7b3e808 100644 --- a/cypress-tests/cypress.config.js +++ b/cypress-tests/cypress.config.js @@ -1,16 +1,17 @@ -const { defineConfig } = require("cypress"); -const fs = require("fs-extra"); -const path = require("path"); +import { defineConfig } from "cypress"; +import mochawesome from "cypress-mochawesome-reporter/plugin.js"; let globalState; + // Fetch from environment variable const connectorId = process.env.CYPRESS_CONNECTOR || "service"; +const screenshotsFolderName = `screenshots/${connectorId}`; const reportName = process.env.REPORT_NAME || `${connectorId}_report`; -module.exports = defineConfig({ +export default defineConfig({ e2e: { setupNodeEvents(on, config) { - require("cypress-mochawesome-reporter/plugin")(on); + mochawesome(on); on("task", { setGlobalState: (val) => { @@ -20,41 +21,21 @@ module.exports = defineConfig({ return globalState || {}; }, cli_log: (message) => { + // eslint-disable-next-line no-console console.log("Logging console message from task"); + // eslint-disable-next-line no-console console.log(message); return null; }, }); - on("after:screenshot", (details) => { - // Full path to the screenshot file - const screenshotPath = details.path; - - // Extract filename without extension - const name = path.basename( - screenshotPath, - path.extname(screenshotPath) - ); - // Define a new name with a connectorId - const newName = `[${connectorId}] ${name}.png`; - const newPath = path.join(path.dirname(screenshotPath), newName); - - return fs - .rename(screenshotPath, newPath) - .then(() => { - console.log("Screenshot renamed successfully"); - return { path: newPath }; - }) - .catch((err) => { - console.error("Failed to rename screenshot:", err); - }); - }); + return config; }, experimentalRunAllSpecs: true, reporter: "cypress-mochawesome-reporter", reporterOptions: { - reportDir: "cypress/reports", + reportDir: `cypress/reports/${connectorId}`, reportFilename: reportName, reportPageTitle: `[${connectorId}] Cypress test report`, embeddedScreenshots: true, @@ -66,4 +47,5 @@ module.exports = defineConfig({ chromeWebSecurity: false, defaultCommandTimeout: 10000, pageLoadTimeout: 20000, + screenshotsFolder: screenshotsFolderName, }); diff --git a/cypress-tests/cypress.env.json b/cypress-tests/cypress.env.json deleted file mode 100644 index 0967ef424bce..000000000000 --- a/cypress-tests/cypress.env.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/cypress-tests/cypress/e2e/PaymentMethodListTest/00000-PaymentMethodListTests.cy.js b/cypress-tests/cypress/e2e/PaymentMethodListTest/00000-PaymentMethodListTests.cy.js index af1f16195c60..89b27a6b2716 100644 --- a/cypress-tests/cypress/e2e/PaymentMethodListTest/00000-PaymentMethodListTests.cy.js +++ b/cypress-tests/cypress/e2e/PaymentMethodListTest/00000-PaymentMethodListTests.cy.js @@ -1,15 +1,13 @@ -import apiKeyCreateBody from "../../fixtures/create-api-key-body.json"; -import createConnectorBody from "../../fixtures/create-connector-body.json"; -import merchantCreateBody from "../../fixtures/merchant-create-body.json"; +import * as fixtures from "../../fixtures/imports"; import State from "../../utils/State"; import { - bank_redirect_ideal_and_credit_enabled, - bank_redirect_ideal_enabled, - card_credit_enabled, - card_credit_enabled_in_US, - card_credit_enabled_in_USD, - create_payment_body_with_currency, - create_payment_body_with_currency_country, + bankRedirectIdealAndCreditEnabled, + bankRedirectIdealEnabled, + cardCreditEnabled, + cardCreditEnabledInUs, + cardCreditEnabledInUsd, + createPaymentBodyWithCurrency, + createPaymentBodyWithCurrencyCountry, } from "../PaymentMethodListUtils/Commons"; import getConnectorDetails from "../PaymentMethodListUtils/Utils"; @@ -34,19 +32,22 @@ describe("Payment Method list using Constraint Graph flow tests", () => { }); it("merchant-create-call-test", () => { - cy.merchantCreateCallTest(merchantCreateBody, globalState); + cy.merchantCreateCallTest(fixtures.merchantCreateBody, globalState); }); it("api-key-create-call-test", () => { - cy.apiKeyCreateTest(apiKeyCreateBody, globalState); + cy.apiKeyCreateTest(fixtures.apiKeyCreateBody, globalState); + }); + it("customer-create-call-test", () => { + cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); }); // stripe connector create with ideal enabled it("connector-create-call-test", () => { cy.createNamedConnectorCallTest( "payment_processor", - createConnectorBody, - bank_redirect_ideal_enabled, + fixtures.createConnectorBody, + bankRedirectIdealEnabled, globalState, "stripe", "stripe_US_default" @@ -57,8 +58,8 @@ describe("Payment Method list using Constraint Graph flow tests", () => { it("connector-create-call-test", () => { cy.createNamedConnectorCallTest( "payment_processor", - createConnectorBody, - card_credit_enabled, + fixtures.createConnectorBody, + cardCreditEnabled, globalState, "cybersource", "cybersource_US_default" @@ -67,14 +68,17 @@ describe("Payment Method list using Constraint Graph flow tests", () => { // creating payment with currency as EUR and no billing address it("create-payment-call-test", () => { - let data = getConnectorDetails("stripe")["pm_list"]["PaymentIntent"]; - let req_data = data["RequestCurrencyEUR"]; - let res_data = data["Response"]; + const data = getConnectorDetails("stripe")["pm_list"]["PaymentIntent"]; + + const newData = { + ...data, + Request: data.RequestCurrencyEUR, + RequestCurrencyEUR: undefined, // we do not need this anymore + }; cy.createPaymentIntentTest( - create_payment_body_with_currency("EUR"), - req_data, - res_data, + createPaymentBodyWithCurrency("EUR"), + newData, "no_three_ds", "automatic", globalState @@ -83,7 +87,7 @@ describe("Payment Method list using Constraint Graph flow tests", () => { // payment method list which should only have ideal with stripe it("payment-method-list-call-test", () => { - let data = + const data = getConnectorDetails("stripe")["pm_list"]["PmListResponse"][ "PmListWithStripeForIdeal" ]; @@ -114,19 +118,19 @@ describe("Payment Method list using Constraint Graph flow tests", () => { }); it("merchant-create-call-test", () => { - cy.merchantCreateCallTest(merchantCreateBody, globalState); + cy.merchantCreateCallTest(fixtures.merchantCreateBody, globalState); }); it("api-key-create-call-test", () => { - cy.apiKeyCreateTest(apiKeyCreateBody, globalState); + cy.apiKeyCreateTest(fixtures.apiKeyCreateBody, globalState); }); // stripe connector create with ideal enabled it("connector-create-call-test", () => { cy.createNamedConnectorCallTest( "payment_processor", - createConnectorBody, - bank_redirect_ideal_enabled, + fixtures.createConnectorBody, + bankRedirectIdealEnabled, globalState, "stripe", "stripe_US_default" @@ -137,8 +141,8 @@ describe("Payment Method list using Constraint Graph flow tests", () => { it("connector-create-call-test", () => { cy.createNamedConnectorCallTest( "payment_processor", - createConnectorBody, - card_credit_enabled_in_USD, + fixtures.createConnectorBody, + cardCreditEnabledInUsd, globalState, "cybersource", "cybersource_US_default" @@ -147,14 +151,17 @@ describe("Payment Method list using Constraint Graph flow tests", () => { // creating payment with currency as INR and no billing address it("create-payment-call-test", () => { - let data = getConnectorDetails("stripe")["pm_list"]["PaymentIntent"]; - let req_data = data["RequestCurrencyINR"]; - let res_data = data["Response"]; + const data = getConnectorDetails("stripe")["pm_list"]["PaymentIntent"]; + + const newData = { + ...data, + Request: data.RequestCurrencyINR, + RequestCurrencyINR: undefined, // we do not need this anymore + }; cy.createPaymentIntentTest( - create_payment_body_with_currency("INR"), - req_data, - res_data, + createPaymentBodyWithCurrency("INR"), + newData, "no_three_ds", "automatic", globalState @@ -163,7 +170,7 @@ describe("Payment Method list using Constraint Graph flow tests", () => { // payment method list which should only have ideal with stripe it("payment-method-list-call-test", () => { - let data = + const data = getConnectorDetails("stripe")["pm_list"]["PmListResponse"][ "PmListNull" ]; @@ -194,19 +201,19 @@ describe("Payment Method list using Constraint Graph flow tests", () => { }); it("merchant-create-call-test", () => { - cy.merchantCreateCallTest(merchantCreateBody, globalState); + cy.merchantCreateCallTest(fixtures.merchantCreateBody, globalState); }); it("api-key-create-call-test", () => { - cy.apiKeyCreateTest(apiKeyCreateBody, globalState); + cy.apiKeyCreateTest(fixtures.apiKeyCreateBody, globalState); }); // stripe connector create with credit enabled for US it("connector-create-call-test", () => { cy.createNamedConnectorCallTest( "payment_processor", - createConnectorBody, - card_credit_enabled_in_US, + fixtures.createConnectorBody, + cardCreditEnabledInUs, globalState, "stripe", "stripe_US_default" @@ -217,8 +224,8 @@ describe("Payment Method list using Constraint Graph flow tests", () => { it("connector-create-call-test", () => { cy.createNamedConnectorCallTest( "payment_processor", - createConnectorBody, - card_credit_enabled_in_US, + fixtures.createConnectorBody, + cardCreditEnabledInUs, globalState, "cybersource", "cybersource_US_default" @@ -227,14 +234,17 @@ describe("Payment Method list using Constraint Graph flow tests", () => { // creating payment with currency as USD and billing address as US it("create-payment-call-test", () => { - let data = getConnectorDetails("stripe")["pm_list"]["PaymentIntent"]; - let req_data = data["RequestCurrencyUSD"]; - let res_data = data["Response"]; + const data = getConnectorDetails("stripe")["pm_list"]["PaymentIntent"]; + + const newData = { + ...data, + Request: data.RequestCurrencyUSD, + RequestCurrencyUSD: undefined, // we do not need this anymore + }; cy.createPaymentIntentTest( - create_payment_body_with_currency("USD"), - req_data, - res_data, + createPaymentBodyWithCurrency("USD"), + newData, "no_three_ds", "automatic", globalState @@ -243,7 +253,7 @@ describe("Payment Method list using Constraint Graph flow tests", () => { // payment method list which should only have credit with Stripe and Cybersource it("payment-method-list-call-test", () => { - let data = + const data = getConnectorDetails("stripe")["pm_list"]["PmListResponse"][ "PmListWithCreditTwoConnector" ]; @@ -274,19 +284,19 @@ describe("Payment Method list using Constraint Graph flow tests", () => { }); it("merchant-create-call-test", () => { - cy.merchantCreateCallTest(merchantCreateBody, globalState); + cy.merchantCreateCallTest(fixtures.merchantCreateBody, globalState); }); it("api-key-create-call-test", () => { - cy.apiKeyCreateTest(apiKeyCreateBody, globalState); + cy.apiKeyCreateTest(fixtures.apiKeyCreateBody, globalState); }); // stripe connector create with ideal enabled it("connector-create-call-test", () => { cy.createNamedConnectorCallTest( "payment_processor", - createConnectorBody, - bank_redirect_ideal_enabled, + fixtures.createConnectorBody, + bankRedirectIdealEnabled, globalState, "stripe", "stripe_US_default" @@ -297,8 +307,8 @@ describe("Payment Method list using Constraint Graph flow tests", () => { it("connector-create-call-test", () => { cy.createNamedConnectorCallTest( "payment_processor", - createConnectorBody, - bank_redirect_ideal_enabled, + fixtures.createConnectorBody, + bankRedirectIdealEnabled, globalState, "cybersource", "cybersource_US_default" @@ -307,14 +317,17 @@ describe("Payment Method list using Constraint Graph flow tests", () => { // creating payment with currency as EUR and billing address as US it("create-payment-call-test", () => { - let data = getConnectorDetails("stripe")["pm_list"]["PaymentIntent"]; - let req_data = data["RequestCurrencyEUR"]; - let res_data = data["Response"]; + const data = getConnectorDetails("stripe")["pm_list"]["PaymentIntent"]; + + const newData = { + ...data, + Request: data.RequestCurrencyEUR, + RequestCurrencyEUR: undefined, // we do not need this anymore + }; cy.createPaymentIntentTest( - create_payment_body_with_currency_country("EUR", "US", "US"), - req_data, - res_data, + createPaymentBodyWithCurrencyCountry("EUR", "US", "US"), + newData, "no_three_ds", "automatic", globalState @@ -323,7 +336,7 @@ describe("Payment Method list using Constraint Graph flow tests", () => { // payment method list which shouldn't have anything it("payment-method-list-call-test", () => { - let data = + const data = getConnectorDetails("stripe")["pm_list"]["PmListResponse"][ "PmListNull" ]; @@ -356,19 +369,19 @@ describe("Payment Method list using Constraint Graph flow tests", () => { }); it("merchant-create-call-test", () => { - cy.merchantCreateCallTest(merchantCreateBody, globalState); + cy.merchantCreateCallTest(fixtures.merchantCreateBody, globalState); }); it("api-key-create-call-test", () => { - cy.apiKeyCreateTest(apiKeyCreateBody, globalState); + cy.apiKeyCreateTest(fixtures.apiKeyCreateBody, globalState); }); // stripe connector create with card credit enabled it("connector-create-call-test", () => { cy.createNamedConnectorCallTest( "payment_processor", - createConnectorBody, - card_credit_enabled, + fixtures.createConnectorBody, + cardCreditEnabled, globalState, "stripe", "stripe_US_default" @@ -379,8 +392,8 @@ describe("Payment Method list using Constraint Graph flow tests", () => { it("connector-create-call-test", () => { cy.createNamedConnectorCallTest( "payment_processor", - createConnectorBody, - bank_redirect_ideal_and_credit_enabled, + fixtures.createConnectorBody, + bankRedirectIdealAndCreditEnabled, globalState, "cybersource", "cybersource_US_default" @@ -389,14 +402,17 @@ describe("Payment Method list using Constraint Graph flow tests", () => { // creating payment with currency as USD and billing address as IN it("create-payment-call-test", () => { - let data = getConnectorDetails("stripe")["pm_list"]["PaymentIntent"]; - let req_data = data["RequestCurrencyUSD"]; - let res_data = data["Response"]; + const data = getConnectorDetails("stripe")["pm_list"]["PaymentIntent"]; + + const newData = { + ...data, + Request: data.RequestCurrencyUSD, + RequestCurrencyUSD: undefined, // we do not need this anymore + }; cy.createPaymentIntentTest( - create_payment_body_with_currency_country("USD", "IN", "IN"), - req_data, - res_data, + createPaymentBodyWithCurrencyCountry("USD", "IN", "IN"), + newData, "no_three_ds", "automatic", globalState @@ -405,7 +421,7 @@ describe("Payment Method list using Constraint Graph flow tests", () => { // payment method list which should have credit with stripe and cybersource and no ideal it("payment-method-list-call-test", () => { - let data = + const data = getConnectorDetails("stripe")["pm_list"]["PmListResponse"][ "PmListWithCreditTwoConnector" ]; @@ -437,19 +453,19 @@ describe("Payment Method list using Constraint Graph flow tests", () => { }); it("merchant-create-call-test", () => { - cy.merchantCreateCallTest(merchantCreateBody, globalState); + cy.merchantCreateCallTest(fixtures.merchantCreateBody, globalState); }); it("api-key-create-call-test", () => { - cy.apiKeyCreateTest(apiKeyCreateBody, globalState); + cy.apiKeyCreateTest(fixtures.apiKeyCreateBody, globalState); }); // stripe connector create with card credit enabled it("connector-create-call-test", () => { cy.createNamedConnectorCallTest( "payment_processor", - createConnectorBody, - card_credit_enabled, + fixtures.createConnectorBody, + cardCreditEnabled, globalState, "stripe", "stripe_US_default" @@ -460,8 +476,8 @@ describe("Payment Method list using Constraint Graph flow tests", () => { it("connector-create-call-test", () => { cy.createNamedConnectorCallTest( "payment_processor", - createConnectorBody, - card_credit_enabled, + fixtures.createConnectorBody, + cardCreditEnabled, globalState, "cybersource", "cybersource_US_default" @@ -470,14 +486,17 @@ describe("Payment Method list using Constraint Graph flow tests", () => { // creating payment with currency as USD and billing address as IN it("create-payment-call-test", () => { - let data = getConnectorDetails("stripe")["pm_list"]["PaymentIntent"]; - let req_data = data["RequestCurrencyUSD"]; - let res_data = data["Response"]; + const data = getConnectorDetails("stripe")["pm_list"]["PaymentIntent"]; + + const newData = { + ...data, + Request: data.RequestCurrencyUSD, + RequestCurrencyUSD: undefined, // we do not need this anymore + }; cy.createPaymentIntentTest( - create_payment_body_with_currency("USD"), - req_data, - res_data, + createPaymentBodyWithCurrency("USD"), + newData, "no_three_ds", "automatic", globalState @@ -486,7 +505,7 @@ describe("Payment Method list using Constraint Graph flow tests", () => { // payment method list which should have credit with stripe and cybersource and no ideal it("payment-method-list-call-test", () => { - let data = + const data = getConnectorDetails("stripe")["pm_list"]["PmListResponse"][ "PmListWithCreditTwoConnector" ]; @@ -517,19 +536,19 @@ describe("Payment Method list using Constraint Graph flow tests", () => { }); it("merchant-create-call-test", () => { - cy.merchantCreateCallTest(merchantCreateBody, globalState); + cy.merchantCreateCallTest(fixtures.merchantCreateBody, globalState); }); it("api-key-create-call-test", () => { - cy.apiKeyCreateTest(apiKeyCreateBody, globalState); + cy.apiKeyCreateTest(fixtures.apiKeyCreateBody, globalState); }); // stripe connector create with ideal enabled it("connector-create-call-test", () => { cy.createNamedConnectorCallTest( "payment_processor", - createConnectorBody, - bank_redirect_ideal_enabled, + fixtures.createConnectorBody, + bankRedirectIdealEnabled, globalState, "stripe", "stripe_US_default" @@ -540,8 +559,8 @@ describe("Payment Method list using Constraint Graph flow tests", () => { it("connector-create-call-test", () => { cy.createNamedConnectorCallTest( "payment_processor", - createConnectorBody, - card_credit_enabled, + fixtures.createConnectorBody, + cardCreditEnabled, globalState, "cybersource", "cybersource_US_default" @@ -550,14 +569,17 @@ describe("Payment Method list using Constraint Graph flow tests", () => { // creating payment with currency as EUR and no billing address it("create-payment-call-test", () => { - let data = getConnectorDetails("stripe")["pm_list"]["PaymentIntent"]; - let req_data = data["RequestCurrencyEUR"]; - let res_data = data["Response"]; + const data = getConnectorDetails("stripe")["pm_list"]["PaymentIntent"]; + + const newData = { + ...data, + Request: data.RequestCurrencyEUR, + RequestCurrencyEUR: undefined, // we do not need this anymore + }; cy.createPaymentIntentTest( - create_payment_body_with_currency_country("EUR", "NL", "US"), - req_data, - res_data, + createPaymentBodyWithCurrencyCountry("EUR", "NL", "US"), + newData, "no_three_ds", "automatic", globalState @@ -566,7 +588,7 @@ describe("Payment Method list using Constraint Graph flow tests", () => { // payment method list which should only have ideal with stripe it("payment-method-list-call-test", () => { - let data = + const data = getConnectorDetails("stripe")["pm_list"]["PmListResponse"][ "PmListWithStripeForIdeal" ]; diff --git a/cypress-tests/cypress/e2e/PaymentMethodListUtils/Commons.js b/cypress-tests/cypress/e2e/PaymentMethodListUtils/Commons.js index a36b4ae1d187..ae72465b6065 100644 --- a/cypress-tests/cypress/e2e/PaymentMethodListUtils/Commons.js +++ b/cypress-tests/cypress/e2e/PaymentMethodListUtils/Commons.js @@ -1,13 +1,4 @@ -import State from "../../utils/State"; - -const globalState = new State({ - connectorId: Cypress.env("CONNECTOR"), - baseUrl: Cypress.env("BASEURL"), - adminApiKey: Cypress.env("ADMINAPIKEY"), - connectorAuthFilePath: Cypress.env("CONNECTOR_AUTH_FILE_PATH"), -}); - -export const card_credit_enabled = [ +export const cardCreditEnabled = [ { payment_method: "card", payment_method_types: [ @@ -15,6 +6,10 @@ export const card_credit_enabled = [ payment_method_type: "credit", card_networks: ["Visa"], minimum_amount: 0, + accepted_currencies: { + type: "enable_only", + list: ["USD"], + }, maximum_amount: 68607706, recurring_enabled: false, installment_payment_enabled: true, @@ -23,7 +18,7 @@ export const card_credit_enabled = [ }, ]; -export const card_credit_enabled_in_USD = [ +export const cardCreditEnabledInUsd = [ { payment_method: "card", payment_method_types: [ @@ -43,7 +38,7 @@ export const card_credit_enabled_in_USD = [ }, ]; -export const card_credit_enabled_in_US = [ +export const cardCreditEnabledInUs = [ { payment_method: "card", payment_method_types: [ @@ -63,7 +58,7 @@ export const card_credit_enabled_in_US = [ }, ]; -export const bank_redirect_ideal_enabled = [ +export const bankRedirectIdealEnabled = [ { payment_method: "bank_redirect", payment_method_types: [ @@ -81,7 +76,7 @@ export const bank_redirect_ideal_enabled = [ }, ]; -export const bank_redirect_ideal_and_credit_enabled = [ +export const bankRedirectIdealAndCreditEnabled = [ { payment_method: "card", payment_method_types: [ @@ -112,7 +107,7 @@ export const bank_redirect_ideal_and_credit_enabled = [ }, ]; -export const create_payment_body_with_currency_country = ( +export const createPaymentBodyWithCurrencyCountry = ( currency, billingCountry, shippingCountry @@ -167,7 +162,7 @@ export const create_payment_body_with_currency_country = ( }, }); -export const create_payment_body_with_currency = (currency) => ({ +export const createPaymentBodyWithCurrency = (currency) => ({ currency: currency, amount: 6500, authentication_type: "three_ds", diff --git a/cypress-tests/cypress/e2e/PaymentMethodListUtils/Stripe.js b/cypress-tests/cypress/e2e/PaymentMethodListUtils/Stripe.js index 9ba678114369..aee9aa0f5361 100644 --- a/cypress-tests/cypress/e2e/PaymentMethodListUtils/Stripe.js +++ b/cypress-tests/cypress/e2e/PaymentMethodListUtils/Stripe.js @@ -1,11 +1,3 @@ -const successfulNo3DSCardDetails = { - card_number: "4242424242424242", - card_exp_month: "10", - card_exp_year: "25", - card_holder_name: "morino", - card_cvc: "737", -}; - export const connectorDetails = { pm_list: { PaymentIntent: { diff --git a/cypress-tests/cypress/e2e/PaymentMethodListUtils/Utils.js b/cypress-tests/cypress/e2e/PaymentMethodListUtils/Utils.js index 9a1d35583c42..f7d199164fd4 100644 --- a/cypress-tests/cypress/e2e/PaymentMethodListUtils/Utils.js +++ b/cypress-tests/cypress/e2e/PaymentMethodListUtils/Utils.js @@ -7,7 +7,7 @@ const connectorDetails = { }; export default function getConnectorDetails(connectorId) { - let x = mergeDetails(connectorId); + const x = mergeDetails(connectorId); return x; } @@ -28,11 +28,11 @@ function getValueByKey(jsonObject, key) { } } -export const should_continue_further = (res_data) => { +export const should_continue_further = (resData) => { if ( - res_data.body.error !== undefined || - res_data.body.error_code !== undefined || - res_data.body.error_message !== undefined + typeof resData.body.error !== "undefined" || + typeof resData.body.error_code !== "undefined" || + typeof resData.body.error_message !== "undefined" ) { return false; } else { diff --git a/cypress-tests/cypress/e2e/PaymentTest/00021-CoreFlows.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00000-CoreFlows.cy.js similarity index 92% rename from cypress-tests/cypress/e2e/PaymentTest/00021-CoreFlows.cy.js rename to cypress-tests/cypress/e2e/PaymentTest/00000-CoreFlows.cy.js index c785c1e68273..a0e2ad643e89 100644 --- a/cypress-tests/cypress/e2e/PaymentTest/00021-CoreFlows.cy.js +++ b/cypress-tests/cypress/e2e/PaymentTest/00000-CoreFlows.cy.js @@ -184,4 +184,20 @@ describe("Core flows", () => { cy.merchantDeleteCall(globalState); }); }); + + context("List Connector Feature Matrix", () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + it("List connector feature matrix call", () => { + cy.ListConnectorsFeatureMatrixCall(globalState); + }); + }); }); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00000-AccountCreate.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00001-AccountCreate.cy.js similarity index 100% rename from cypress-tests/cypress/e2e/PaymentTest/00000-AccountCreate.cy.js rename to cypress-tests/cypress/e2e/PaymentTest/00001-AccountCreate.cy.js index 5753421a7458..1788a369faf6 100644 --- a/cypress-tests/cypress/e2e/PaymentTest/00000-AccountCreate.cy.js +++ b/cypress-tests/cypress/e2e/PaymentTest/00001-AccountCreate.cy.js @@ -1,5 +1,5 @@ -import State from "../../utils/State"; import * as fixtures from "../../fixtures/imports"; +import State from "../../utils/State"; let globalState; describe("Account Create flow test", () => { diff --git a/cypress-tests/cypress/e2e/PaymentTest/00002-ConnectorCreate.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00002-ConnectorCreate.cy.js deleted file mode 100644 index 7a8b7b633024..000000000000 --- a/cypress-tests/cypress/e2e/PaymentTest/00002-ConnectorCreate.cy.js +++ /dev/null @@ -1,25 +0,0 @@ -import * as fixtures from "../../fixtures/imports"; -import State from "../../utils/State"; -import { payment_methods_enabled } from "../PaymentUtils/Commons"; - -let globalState; -describe("Connector Account Create flow test", () => { - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - after("flush global state", () => { - cy.task("setGlobalState", globalState.data); - }); - - it("connector-create-call-test", () => { - cy.createConnectorCallTest( - "payment_processor", - fixtures.createConnectorBody, - payment_methods_enabled, - globalState - ); - }); -}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00001-CustomerCreate.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00002-CustomerCreate.cy.js similarity index 100% rename from cypress-tests/cypress/e2e/PaymentTest/00001-CustomerCreate.cy.js rename to cypress-tests/cypress/e2e/PaymentTest/00002-CustomerCreate.cy.js diff --git a/cypress-tests/cypress/e2e/PaymentTest/00003-ConnectorCreate.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00003-ConnectorCreate.cy.js new file mode 100644 index 000000000000..3d002740e60e --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentTest/00003-ConnectorCreate.cy.js @@ -0,0 +1,50 @@ +import * as fixtures from "../../fixtures/imports"; +import State from "../../utils/State"; +import { payment_methods_enabled } from "../PaymentUtils/Commons"; +import * as utils from "../PaymentUtils/Utils"; + +let globalState; +describe("Connector Account Create flow test", () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + it("Create merchant connector account", () => { + cy.createConnectorCallTest( + "payment_processor", + fixtures.createConnectorBody, + payment_methods_enabled, + globalState + ); + }); + + // subsequent profile and mca ids should check for the existence of multiple connectors + context( + "Create another business profile and merchant connector account if MULTIPLE_CONNECTORS flag is true", + () => { + it("Create business profile", () => { + utils.createBusinessProfile( + fixtures.businessProfile.bpCreate, + globalState, + { nextConnector: true } + ); + }); + + it("Create merchant connector account", () => { + utils.createMerchantConnectorAccount( + "payment_processor", + fixtures.createConnectorBody, + globalState, + payment_methods_enabled, + { nextConnector: true } + ); + }); + } + ); +}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00003-NoThreeDSAutoCapture.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00003-NoThreeDSAutoCapture.cy.js deleted file mode 100644 index e8c719cebc74..000000000000 --- a/cypress-tests/cypress/e2e/PaymentTest/00003-NoThreeDSAutoCapture.cy.js +++ /dev/null @@ -1,103 +0,0 @@ -import * as fixtures from "../../fixtures/imports"; -import State from "../../utils/State"; -import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; - -let globalState; - -describe("Card - NoThreeDS payment flow test", () => { - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - after("flush global state", () => { - cy.task("setGlobalState", globalState.data); - }); - - 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 data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ - "PaymentIntent" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createPaymentIntentTest( - fixtures.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", () => { - cy.paymentMethodsCallTest(globalState); - }); - - it("Confirm No 3DS", () => { - let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ - "No3DSAutoCapture" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.confirmCallTest( - fixtures.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); - }); - }); - - 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 data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ - "No3DSAutoCapture" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createConfirmPaymentTest( - fixtures.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); - }); - }); -}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00004-NoThreeDSAutoCapture.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00004-NoThreeDSAutoCapture.cy.js new file mode 100644 index 000000000000..e5030647cb7c --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentTest/00004-NoThreeDSAutoCapture.cy.js @@ -0,0 +1,147 @@ +import * as fixtures from "../../fixtures/imports"; +import State from "../../utils/State"; +import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; + +let globalState; + +describe("Card - NoThreeDS payment flow test", () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + context("Card-NoThreeDS payment flow test Create and confirm", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("Confirm No 3DS", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + }); + + context("Card-NoThreeDS payment flow test Create+Confirm", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create+confirm-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.createConfirmPaymentTest( + fixtures.createConfirmPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + }); + + context("Card-NoThreeDS payment with shipping cost", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentWithShippingCost"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("Confirm No 3DS", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentConfirmWithShippingCost"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentConfirmWithShippingCost"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + }); +}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00005-NoThreeDSManualCapture.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00005-NoThreeDSManualCapture.cy.js deleted file mode 100644 index 6a74e6fb910a..000000000000 --- a/cypress-tests/cypress/e2e/PaymentTest/00005-NoThreeDSManualCapture.cy.js +++ /dev/null @@ -1,289 +0,0 @@ -import * as fixtures from "../../fixtures/imports"; -import State from "../../utils/State"; -import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; - -let globalState; - -describe("Card - NoThreeDS Manual payment flow test", () => { - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - after("flush global state", () => { - cy.task("setGlobalState", globalState.data); - }); - - 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 data = getConnectorDetails(globalState.get("connectorId"))[ - "card_pm" - ]["PaymentIntent"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createPaymentIntentTest( - fixtures.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", () => { - cy.paymentMethodsCallTest(globalState); - }); - - it("confirm-call-test", () => { - console.log("confirm -> " + globalState.get("connectorId")); - 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( - fixtures.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("capture-call-test", () => { - 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( - fixtures.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); - }); - }); - - 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 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( - fixtures.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", () => { - 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"]; - console.log("det -> " + data.card); - cy.captureCallTest( - fixtures.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); - }); - }); - }); - - 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 data = getConnectorDetails(globalState.get("connectorId"))[ - "card_pm" - ]["PaymentIntent"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createPaymentIntentTest( - fixtures.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", () => { - cy.paymentMethodsCallTest(globalState); - }); - - it("confirm-call-test", () => { - console.log("confirm -> " + globalState.get("connectorId")); - 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( - fixtures.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("capture-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ - "card_pm" - ]["PartialCapture"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.captureCallTest( - fixtures.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); - }); - }); - - 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 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( - fixtures.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", () => { - 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"]; - console.log("det -> " + data.card); - cy.captureCallTest( - fixtures.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); - }); - }); - } - ); -}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00004-ThreeDSAutoCapture.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00005-ThreeDSAutoCapture.cy.js similarity index 54% rename from cypress-tests/cypress/e2e/PaymentTest/00004-ThreeDSAutoCapture.cy.js rename to cypress-tests/cypress/e2e/PaymentTest/00005-ThreeDSAutoCapture.cy.js index 91dc2b1c382c..3a0b635f79a2 100644 --- a/cypress-tests/cypress/e2e/PaymentTest/00004-ThreeDSAutoCapture.cy.js +++ b/cypress-tests/cypress/e2e/PaymentTest/00005-ThreeDSAutoCapture.cy.js @@ -5,10 +5,10 @@ import getConnectorDetails, * as utils from "../PaymentUtils/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 + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails beforeEach(function () { - if (!should_continue) { + if (!shouldContinue) { this.skip(); } }); @@ -24,21 +24,19 @@ describe("Card - ThreeDS payment flow test", () => { }); it("create-payment-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ + const data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ "PaymentIntent" ]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createPaymentIntentTest( fixtures.createPaymentBody, - req_data, - res_data, + data, "three_ds", "automatic", globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); it("payment_methods-call-test", () => { @@ -46,24 +44,17 @@ describe("Card - ThreeDS payment flow test", () => { }); it("Confirm 3DS", () => { - let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ + const data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ "3DSAutoCapture" ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.confirmCallTest( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); it("Handle redirection", () => { - let expected_redirection = fixtures.confirmBody["return_url"]; + const expected_redirection = fixtures.confirmBody["return_url"]; cy.handleRedirection(globalState, expected_redirection); }); }); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00006-NoThreeDSManualCapture.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00006-NoThreeDSManualCapture.cy.js new file mode 100644 index 000000000000..d9769daab4b6 --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentTest/00006-NoThreeDSManualCapture.cy.js @@ -0,0 +1,270 @@ +import * as fixtures from "../../fixtures/imports"; +import State from "../../utils/State"; +import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; + +let globalState; + +describe("Card - NoThreeDS Manual payment flow test", () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + context("Card - NoThreeDS Manual Full Capture payment flow test", () => { + context("payment Create and Confirm", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "manual", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("confirm-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSManualCapture"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSManualCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("capture-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + }); + + context("Payment Create+Confirm", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create+confirm-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSManualCapture"]; + + cy.createConfirmPaymentTest( + fixtures.createConfirmPaymentBody, + data, + "no_three_ds", + "manual", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSManualCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("capture-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + }); + }); + + context( + "Card - NoThreeDS Manual Partial Capture payment flow test - Create and Confirm", + () => { + context("payment Create and Payment Confirm", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "manual", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("confirm-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSManualCapture"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSManualCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("capture-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PartialCapture"]; + + cy.captureCallTest(fixtures.captureBody, data, 100, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PartialCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + }); + + context("payment + Confirm", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create+confirm-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSManualCapture"]; + + cy.createConfirmPaymentTest( + fixtures.createConfirmPaymentBody, + data, + "no_three_ds", + "manual", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSManualCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("capture-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PartialCapture"]; + + cy.captureCallTest(fixtures.captureBody, data, 100, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PartialCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + }); + } + ); +}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00006-VoidPayment.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00006-VoidPayment.cy.js deleted file mode 100644 index 9735a74adfd1..000000000000 --- a/cypress-tests/cypress/e2e/PaymentTest/00006-VoidPayment.cy.js +++ /dev/null @@ -1,190 +0,0 @@ -import * as fixtures from "../../fixtures/imports"; -import State from "../../utils/State"; -import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; - -let globalState; - -describe("Card - NoThreeDS Manual payment flow test", () => { - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - after("flush global state", () => { - cy.task("setGlobalState", globalState.data); - }); - - 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 data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ - "PaymentIntent" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createPaymentIntentTest( - fixtures.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", () => { - cy.paymentMethodsCallTest(globalState); - }); - - it("confirm-call-test", () => { - console.log("confirm -> " + globalState.get("connectorId")); - 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( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - - it("void-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ - "Void" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.voidCallTest(fixtures.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 data = getConnectorDetails(globalState.get("connectorId"))[ - "card_pm" - ]["PaymentIntent"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createPaymentIntentTest( - fixtures.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", () => { - cy.paymentMethodsCallTest(globalState); - }); - - it("void-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ - "card_pm" - ]["Void"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.voidCallTest(fixtures.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 data = getConnectorDetails(globalState.get("connectorId"))[ - "card_pm" - ]["PaymentIntent"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createPaymentIntentTest( - fixtures.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", () => { - cy.paymentMethodsCallTest(globalState); - }); - - it("confirm-call-test", () => { - console.log("confirm -> " + globalState.get("connectorId")); - 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( - fixtures.confirmBody, - req_data, - res_data, - false, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - - it("void-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ - "card_pm" - ]["Void"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.voidCallTest(fixtures.voidBody, req_data, res_data, globalState); - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - } - ); -}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00007-VoidPayment.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00007-VoidPayment.cy.js new file mode 100644 index 000000000000..0050aa39bfc1 --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentTest/00007-VoidPayment.cy.js @@ -0,0 +1,162 @@ +import * as fixtures from "../../fixtures/imports"; +import State from "../../utils/State"; +import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; + +let globalState; + +describe("Card - NoThreeDS Manual payment flow test", () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + context("Card - void payment in Requires_capture state flow test", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "manual", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("confirm-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSManualCapture"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("void-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["VoidAfterConfirm"]; + + cy.voidCallTest(fixtures.voidBody, data, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); + + context( + "Card - void payment in Requires_payment_method state flow test", + () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "manual", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("void-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Void"]; + + cy.voidCallTest(fixtures.voidBody, data, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + } + ); + + context("Card - void payment in success state flow test", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "manual", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("confirm-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSManualCapture"]; + + cy.confirmCallTest(fixtures.confirmBody, data, false, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("void-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["VoidAfterConfirm"]; + + cy.voidCallTest(fixtures.voidBody, data, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); +}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00008-RefundPayment.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00008-RefundPayment.cy.js deleted file mode 100644 index 70157e06ec61..000000000000 --- a/cypress-tests/cypress/e2e/PaymentTest/00008-RefundPayment.cy.js +++ /dev/null @@ -1,1666 +0,0 @@ -import * as fixtures from "../../fixtures/imports"; -import State from "../../utils/State"; -import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; - -let globalState; - -describe("Card - Refund flow - No 3DS", () => { - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - afterEach("flush global state", () => { - cy.task("setGlobalState", globalState.data); - }); - - 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 data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ - "PaymentIntent" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createPaymentIntentTest( - fixtures.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", () => { - cy.paymentMethodsCallTest(globalState); - }); - - it("confirm-call-test", () => { - console.log("confirm -> " + globalState.get("connectorId")); - 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( - fixtures.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 data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ - "Refund" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.refundCallTest( - fixtures.refundBody, - req_data, - res_data, - 6500, - 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 - 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 data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ - "PaymentIntent" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createPaymentIntentTest( - fixtures.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", () => { - cy.paymentMethodsCallTest(globalState); - }); - - it("confirm-call-test", () => { - console.log("confirm -> " + globalState.get("connectorId")); - 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( - fixtures.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 data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ - "PartialRefund" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.refundCallTest( - fixtures.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( - fixtures.refundBody, - req_data, - res_data, - 1200, - 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( - "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 data = getConnectorDetails(globalState.get("connectorId"))[ - "card_pm" - ]["No3DSAutoCapture"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createConfirmPaymentTest( - fixtures.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("refund-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ - "card_pm" - ]["Refund"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.refundCallTest( - fixtures.refundBody, - req_data, - res_data, - 6500, - 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( - "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 data = getConnectorDetails(globalState.get("connectorId"))[ - "card_pm" - ]["No3DSAutoCapture"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createConfirmPaymentTest( - fixtures.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("refund-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ - "card_pm" - ]["PartialRefund"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.refundCallTest( - fixtures.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( - fixtures.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 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 data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ - "PaymentIntent" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createPaymentIntentTest( - fixtures.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", () => { - cy.paymentMethodsCallTest(globalState); - }); - - it("confirm-call-test", () => { - console.log("confirm -> " + globalState.get("connectorId")); - 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( - fixtures.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("capture-call-test", () => { - 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( - fixtures.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( - fixtures.refundBody, - req_data, - res_data, - 6500, - 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 - 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 data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ - "PaymentIntent" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createPaymentIntentTest( - fixtures.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", () => { - cy.paymentMethodsCallTest(globalState); - }); - - it("confirm-call-test", () => { - console.log("confirm -> " + globalState.get("connectorId")); - 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( - fixtures.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("capture-call-test", () => { - 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( - fixtures.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"][ - "PartialRefund" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.refundCallTest( - fixtures.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( - fixtures.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); - }); - it("list-refund-call-test", () => { - cy.listRefundCallTest(fixtures.listRefundCall, globalState); - }); - }); - - 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 data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ - "PaymentIntent" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createPaymentIntentTest( - fixtures.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", () => { - cy.paymentMethodsCallTest(globalState); - }); - - it("confirm-call-test", () => { - console.log("confirm -> " + globalState.get("connectorId")); - 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( - fixtures.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("capture-call-test", () => { - 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( - fixtures.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( - fixtures.refundBody, - req_data, - res_data, - 100, - 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 - 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 data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ - "PaymentIntent" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createPaymentIntentTest( - fixtures.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", () => { - cy.paymentMethodsCallTest(globalState); - }); - - it("confirm-call-test", () => { - console.log("confirm -> " + globalState.get("connectorId")); - 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( - fixtures.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("capture-call-test", () => { - 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( - fixtures.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"][ - "PartialRefund" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.refundCallTest( - fixtures.refundBody, - req_data, - res_data, - 100, - 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 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 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( - fixtures.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", () => { - cy.mitForMandatesCallTest( - fixtures.mitConfirmBody, - 7000, - true, - "automatic", - globalState - ); - }); - - it("Confirm No 3DS MIT", () => { - cy.mitForMandatesCallTest( - fixtures.mitConfirmBody, - 7000, - true, - "automatic", - 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( - fixtures.refundBody, - req_data, - res_data, - 7000, - 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); - }); - } - ); -}); - -describe("Card - Refund flow - 3DS", () => { - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - afterEach("flush global state", () => { - cy.task("setGlobalState", globalState.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( - fixtures.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"]; - cy.confirmCallTest( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - - it("Handle redirection", () => { - let expected_redirection = fixtures.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( - fixtures.refundBody, - req_data, - res_data, - 6500, - 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 - 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( - fixtures.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( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - - it("Handle redirection", () => { - let expected_redirection = fixtures.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( - fixtures.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( - fixtures.refundBody, - req_data, - res_data, - 1200, - 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("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( - fixtures.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 = fixtures.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( - fixtures.refundBody, - req_data, - res_data, - 6500, - 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( - "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( - fixtures.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 = fixtures.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( - fixtures.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( - fixtures.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( - fixtures.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( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - - it("Handle redirection", () => { - let expected_redirection = fixtures.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( - fixtures.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( - fixtures.refundBody, - req_data, - res_data, - 6500, - 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 - 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( - fixtures.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( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - - it("Handle redirection", () => { - let expected_redirection = fixtures.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( - fixtures.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( - fixtures.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( - fixtures.refundBody, - req_data, - res_data, - 1500, - 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 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( - fixtures.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( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - - it("Handle redirection", () => { - let expected_redirection = fixtures.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( - fixtures.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( - fixtures.refundBody, - req_data, - res_data, - 100, - 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 - 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( - fixtures.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( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - - it("Handle redirection", () => { - let expected_redirection = fixtures.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( - fixtures.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( - fixtures.refundBody, - req_data, - res_data, - 50, - 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); - }); - }); -}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00007-SyncPayment.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00008-SyncPayment.cy.js similarity index 51% rename from cypress-tests/cypress/e2e/PaymentTest/00007-SyncPayment.cy.js rename to cypress-tests/cypress/e2e/PaymentTest/00008-SyncPayment.cy.js index 95d86a62278a..370d811ed628 100644 --- a/cypress-tests/cypress/e2e/PaymentTest/00007-SyncPayment.cy.js +++ b/cypress-tests/cypress/e2e/PaymentTest/00008-SyncPayment.cy.js @@ -5,10 +5,10 @@ import getConnectorDetails, * as utils from "../PaymentUtils/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 + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails beforeEach(function () { - if (!should_continue) { + if (!shouldContinue) { this.skip(); } }); @@ -23,21 +23,19 @@ describe("Card - Sync payment flow test", () => { cy.task("setGlobalState", globalState.data); }); it("create-payment-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ + const data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ "PaymentIntent" ]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createPaymentIntentTest( fixtures.createPaymentBody, - req_data, - res_data, + data, "no_three_ds", "automatic", globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); it("payment_methods-call-test", () => { @@ -45,25 +43,19 @@ describe("Card - Sync payment flow test", () => { }); it("confirm-call-test", () => { - console.log("confirm -> " + globalState.get("connectorId")); - let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ + const 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( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); it("retrieve-payment-call-test", () => { - cy.retrievePaymentCallTest(globalState); + const data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ + "No3DSAutoCapture" + ]; + cy.retrievePaymentCallTest(globalState, data); }); }); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00009-RefundPayment.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00009-RefundPayment.cy.js new file mode 100644 index 000000000000..4d534127ada2 --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentTest/00009-RefundPayment.cy.js @@ -0,0 +1,1410 @@ +import * as fixtures from "../../fixtures/imports"; +import State from "../../utils/State"; +import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; + +let globalState; + +describe("Card - Refund flow - No 3DS", () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + afterEach("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + context("Card - Full Refund flow test for No-3DS", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("confirm-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Refund"]; + + cy.refundCallTest(fixtures.refundBody, data, 6500, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("sync-refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SyncRefund"]; + + cy.syncRefundCallTest(data, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); + + context("Card - Partial Refund flow test for No-3DS", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("confirm-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PartialRefund"]; + + cy.refundCallTest(fixtures.refundBody, data, 1200, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PartialRefund"]; + + cy.refundCallTest(fixtures.refundBody, data, 1200, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("sync-refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SyncRefund"]; + + cy.syncRefundCallTest(data, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); + + context( + "Fully Refund Card-NoThreeDS payment flow test Create+Confirm", + () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create+confirm-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.createConfirmPaymentTest( + fixtures.createConfirmPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Refund"]; + + cy.refundCallTest(fixtures.refundBody, data, 6500, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("sync-refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SyncRefund"]; + + cy.syncRefundCallTest(data, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + } + ); + + context( + "Partially Refund Card-NoThreeDS payment flow test Create+Confirm", + () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create+confirm-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.createConfirmPaymentTest( + fixtures.createConfirmPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PartialRefund"]; + + cy.refundCallTest(fixtures.refundBody, data, 3000, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PartialRefund"]; + + cy.refundCallTest(fixtures.refundBody, data, 3000, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("sync-refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SyncRefund"]; + + cy.syncRefundCallTest(data, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + } + ); + + context("Card - Full Refund for fully captured No-3DS payment", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "manual", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("confirm-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSManualCapture"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSManualCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("capture-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["manualPaymentRefund"]; + + cy.refundCallTest(fixtures.refundBody, data, 6500, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("sync-refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SyncRefund"]; + + cy.syncRefundCallTest(data, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); + + context("Card - Partial Refund for fully captured No-3DS payment", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "manual", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("confirm-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSManualCapture"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSManualCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("capture-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["manualPaymentPartialRefund"]; + + cy.refundCallTest(fixtures.refundBody, data, 3000, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + it("refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["manualPaymentPartialRefund"]; + + cy.refundCallTest(fixtures.refundBody, data, 3000, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("sync-refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SyncRefund"]; + + cy.syncRefundCallTest(data, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + it("list-refund-call-test", () => { + cy.listRefundCallTest(fixtures.listRefundCall, globalState); + }); + }); + + context("Card - Full Refund for partially captured No-3DS payment", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "manual", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("confirm-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSManualCapture"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSManualCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("capture-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PartialCapture"]; + + cy.captureCallTest(fixtures.captureBody, data, 100, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PartialCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["manualPaymentRefund"]; + + cy.refundCallTest(fixtures.refundBody, data, 100, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("sync-refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SyncRefund"]; + + cy.syncRefundCallTest(data, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); + + context("Card - partial Refund for partially captured No-3DS payment", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "manual", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("confirm-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSManualCapture"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSManualCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("capture-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PartialCapture"]; + + cy.captureCallTest(fixtures.captureBody, data, 100, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PartialCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["manualPaymentPartialRefund"]; + + cy.refundCallTest(fixtures.refundBody, data, 100, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("sync-refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SyncRefund"]; + + cy.syncRefundCallTest(data, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); + + context( + "Card - Full Refund for Create + Confirm Automatic CIT and MIT payment flow test", + () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Confirm No 3DS CIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MandateMultiUseNo3DSAutoCapture"]; + + cy.citForMandatesCallTest( + fixtures.citConfirmBody, + data, + 7000, + true, + "automatic", + "new_mandate", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + + cy.mitForMandatesCallTest( + fixtures.mitConfirmBody, + data, + 7000, + true, + "automatic", + globalState + ); + }); + + it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + + cy.mitForMandatesCallTest( + fixtures.mitConfirmBody, + data, + 7000, + true, + "automatic", + globalState + ); + }); + + it("refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Refund"]; + + cy.refundCallTest(fixtures.refundBody, data, 7000, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("sync-refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SyncRefund"]; + + cy.syncRefundCallTest(data, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + } + ); +}); + +describe("Card - Refund flow - 3DS", () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + afterEach("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + context("Card - Full Refund flow test for 3DS", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "three_ds", + "automatic", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("Confirm 3DS", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSAutoCapture"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Handle redirection", () => { + const expected_redirection = fixtures.confirmBody["return_url"]; + cy.handleRedirection(globalState, expected_redirection); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSAutoCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Refund"]; + + cy.refundCallTest(fixtures.refundBody, data, 6500, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("sync-refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SyncRefund"]; + + cy.syncRefundCallTest(data, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); + + context("Card - Partial Refund flow test for 3DS", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "three_ds", + "automatic", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("Confirm 3DS", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSAutoCapture"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Handle redirection", () => { + const expected_redirection = fixtures.confirmBody["return_url"]; + cy.handleRedirection(globalState, expected_redirection); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSAutoCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PartialRefund"]; + + cy.refundCallTest(fixtures.refundBody, data, 1200, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PartialRefund"]; + + cy.refundCallTest(fixtures.refundBody, data, 1200, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("sync-refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SyncRefund"]; + + cy.syncRefundCallTest(data, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); + + context("Fully Refund Card-ThreeDS payment flow test Create+Confirm", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create+confirm-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSAutoCapture"]; + + cy.createConfirmPaymentTest( + fixtures.createConfirmPaymentBody, + data, + "three_ds", + "automatic", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Handle redirection", () => { + const expected_redirection = fixtures.confirmBody["return_url"]; + cy.handleRedirection(globalState, expected_redirection); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSAutoCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Refund"]; + + cy.refundCallTest(fixtures.refundBody, data, 6500, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("sync-refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SyncRefund"]; + + cy.syncRefundCallTest(data, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); + + context( + "Partially Refund Card-ThreeDS payment flow test Create+Confirm", + () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create+confirm-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSAutoCapture"]; + + cy.createConfirmPaymentTest( + fixtures.createConfirmPaymentBody, + data, + "three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Handle redirection", () => { + const expected_redirection = fixtures.confirmBody["return_url"]; + cy.handleRedirection(globalState, expected_redirection); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSAutoCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PartialRefund"]; + + cy.refundCallTest(fixtures.refundBody, data, 3000, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PartialRefund"]; + + cy.refundCallTest(fixtures.refundBody, data, 3000, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("sync-refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SyncRefund"]; + + cy.syncRefundCallTest(data, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + } + ); + + context("Card - Full Refund for fully captured 3DS payment", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "three_ds", + "manual", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("Confirm 3DS", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSManualCapture"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Handle redirection", () => { + const expected_redirection = fixtures.confirmBody["return_url"]; + cy.handleRedirection(globalState, expected_redirection); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSManualCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("capture-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["manualPaymentRefund"]; + + cy.refundCallTest(fixtures.refundBody, data, 6500, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("sync-refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SyncRefund"]; + + cy.syncRefundCallTest(data, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); + + context("Card - Partial Refund for fully captured 3DS payment", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "three_ds", + "manual", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("Confirm 3DS", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSManualCapture"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Handle redirection", () => { + const expected_redirection = fixtures.confirmBody["return_url"]; + cy.handleRedirection(globalState, expected_redirection); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSManualCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("capture-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["manualPaymentPartialRefund"]; + + cy.refundCallTest(fixtures.refundBody, data, 5000, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + it("refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["manualPaymentPartialRefund"]; + + cy.refundCallTest(fixtures.refundBody, data, 1500, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("sync-refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SyncRefund"]; + + cy.syncRefundCallTest(data, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); + + context("Card - Full Refund for partially captured 3DS payment", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "three_ds", + "manual", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("Confirm 3DS", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSManualCapture"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Handle redirection", () => { + const expected_redirection = fixtures.confirmBody["return_url"]; + cy.handleRedirection(globalState, expected_redirection); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSManualCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("capture-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PartialCapture"]; + + cy.captureCallTest(fixtures.captureBody, data, 100, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PartialCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["manualPaymentRefund"]; + + cy.refundCallTest(fixtures.refundBody, data, 100, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("sync-refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SyncRefund"]; + + cy.syncRefundCallTest(data, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); + + context("Card - partial Refund for partially captured 3DS payment", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "three_ds", + "manual", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("Confirm 3DS", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSManualCapture"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Handle redirection", () => { + const expected_redirection = fixtures.confirmBody["return_url"]; + cy.handleRedirection(globalState, expected_redirection); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSManualCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("capture-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PartialCapture"]; + + cy.captureCallTest(fixtures.captureBody, data, 100, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PartialCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["manualPaymentRefund"]; + + cy.refundCallTest(fixtures.refundBody, data, 50, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("sync-refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SyncRefund"]; + + cy.syncRefundCallTest(data, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); +}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00009-SyncRefund.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00009-SyncRefund.cy.js deleted file mode 100644 index 617a0e721b94..000000000000 --- a/cypress-tests/cypress/e2e/PaymentTest/00009-SyncRefund.cy.js +++ /dev/null @@ -1,98 +0,0 @@ -import * as fixtures from "../../fixtures/imports"; -import State from "../../utils/State"; -import getConnectorDetails, * as utils from "../PaymentUtils/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", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - after("flush global state", () => { - cy.task("setGlobalState", globalState.data); - }); - - 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( - fixtures.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", () => { - cy.paymentMethodsCallTest(globalState); - }); - - it("confirm-call-test", () => { - console.log("confirm -> " + globalState.get("connectorId")); - 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( - fixtures.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 data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ - "Refund" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.refundCallTest( - fixtures.refundBody, - req_data, - res_data, - 6500, - 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); - }); -}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00010-SyncRefund.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00010-SyncRefund.cy.js new file mode 100644 index 000000000000..dc7386211666 --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentTest/00010-SyncRefund.cy.js @@ -0,0 +1,83 @@ +import * as fixtures from "../../fixtures/imports"; +import State from "../../utils/State"; +import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; + +let globalState; + +describe("Card - Sync Refund flow test", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ + "PaymentIntent" + ]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("confirm-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ + "No3DSAutoCapture" + ]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ + "No3DSAutoCapture" + ]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ + "Refund" + ]; + + cy.refundCallTest(fixtures.refundBody, data, 6500, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("sync-refund-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ + "SyncRefund" + ]; + + cy.syncRefundCallTest(data, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); +}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00011-CreateMultiuseMandate.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00011-CreateMultiuseMandate.cy.js deleted file mode 100644 index 3596af70fe1b..000000000000 --- a/cypress-tests/cypress/e2e/PaymentTest/00011-CreateMultiuseMandate.cy.js +++ /dev/null @@ -1,243 +0,0 @@ -import * as fixtures from "../../fixtures/imports"; -import State from "../../utils/State"; -import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; - -let globalState; - -describe("Card - MultiUse Mandates flow test", () => { - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - after("flush global state", () => { - cy.task("setGlobalState", globalState.data); - }); - - 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 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( - fixtures.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", () => { - cy.mitForMandatesCallTest( - fixtures.mitConfirmBody, - 7000, - true, - "automatic", - globalState - ); - }); - it("Confirm No 3DS MIT", () => { - cy.mitForMandatesCallTest( - fixtures.mitConfirmBody, - 7000, - true, - "automatic", - globalState - ); - }); - } - ); - - 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 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( - fixtures.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 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( - fixtures.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( - fixtures.mitConfirmBody, - 6500, - true, - "manual", - globalState - ); - }); - - it("mit-capture-call-test", () => { - 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( - fixtures.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( - fixtures.mitConfirmBody, - 6500, - true, - "manual", - globalState - ); - }); - - it("mit-capture-call-test", () => { - 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( - fixtures.captureBody, - req_data, - res_data, - 6500, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - } - ); - - 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 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( - fixtures.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 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( - fixtures.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( - fixtures.mitConfirmBody, - 6500, - true, - "automatic", - globalState - ); - }); - } - ); -}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00010-CreateSingleuseMandate.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00011-CreateSingleuseMandate.cy.js similarity index 50% rename from cypress-tests/cypress/e2e/PaymentTest/00010-CreateSingleuseMandate.cy.js rename to cypress-tests/cypress/e2e/PaymentTest/00011-CreateSingleuseMandate.cy.js index 5c1409138263..da2123d64623 100644 --- a/cypress-tests/cypress/e2e/PaymentTest/00010-CreateSingleuseMandate.cy.js +++ b/cypress-tests/cypress/e2e/PaymentTest/00011-CreateSingleuseMandate.cy.js @@ -18,39 +18,41 @@ 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 + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails beforeEach(function () { - if (!should_continue) { + if (!shouldContinue) { this.skip(); } }); it("Confirm No 3DS CIT", () => { - console.log("confirm -> " + globalState.get("connectorId")); - let data = getConnectorDetails(globalState.get("connectorId"))[ + const 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( fixtures.citConfirmBody, - req_data, - res_data, + data, 7000, true, "automatic", "new_mandate", globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); }); it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + cy.mitForMandatesCallTest( fixtures.mitConfirmBody, + data, 7000, true, "automatic", @@ -63,57 +65,52 @@ 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 + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails beforeEach(function () { - if (!should_continue) { + if (!shouldContinue) { this.skip(); } }); it("Confirm No 3DS CIT", () => { - console.log("confirm -> " + globalState.get("connectorId")); - let data = getConnectorDetails(globalState.get("connectorId"))[ + const 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( fixtures.citConfirmBody, - req_data, - res_data, + data, 6500, true, "manual", "new_mandate", globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); }); it("cit-capture-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ + const 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( - fixtures.captureBody, - req_data, - res_data, - 6500, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); }); it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITManualCapture"]; + cy.mitForMandatesCallTest( fixtures.mitConfirmBody, + data, 6500, true, "manual", @@ -122,21 +119,14 @@ describe("Card - SingleUse Mandates flow test", () => { }); it("mit-capture-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ + const 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( - fixtures.captureBody, - req_data, - res_data, - 6500, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); }); it("list-mandate-call-test", () => { @@ -148,57 +138,52 @@ describe("Card - SingleUse Mandates flow test", () => { context( "Card - No 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 + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails beforeEach(function () { - if (!should_continue) { + if (!shouldContinue) { this.skip(); } }); it("Create No 3DS CIT", () => { - console.log("confirm -> " + globalState.get("connectorId")); - let data = getConnectorDetails(globalState.get("connectorId"))[ + const 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( fixtures.citConfirmBody, - req_data, - res_data, + data, 6500, true, "manual", "new_mandate", globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); }); it("cit-capture-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ + const 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( - fixtures.captureBody, - req_data, - res_data, - 6500, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); }); it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + cy.mitForMandatesCallTest( fixtures.mitConfirmBody, + data, 7000, true, "automatic", diff --git a/cypress-tests/cypress/e2e/PaymentTest/00012-CreateMultiuseMandate.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00012-CreateMultiuseMandate.cy.js new file mode 100644 index 000000000000..000d69c45e68 --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentTest/00012-CreateMultiuseMandate.cy.js @@ -0,0 +1,231 @@ +import * as fixtures from "../../fixtures/imports"; +import State from "../../utils/State"; +import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; + +let globalState; + +describe("Card - MultiUse Mandates flow test", () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + context( + "Card - NoThreeDS Create + Confirm Automatic CIT and MIT payment flow test", + () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Confirm No 3DS CIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MandateMultiUseNo3DSAutoCapture"]; + + cy.citForMandatesCallTest( + fixtures.citConfirmBody, + data, + 7000, + true, + "automatic", + "new_mandate", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + + cy.mitForMandatesCallTest( + fixtures.mitConfirmBody, + data, + 7000, + true, + "automatic", + globalState + ); + }); + it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + + cy.mitForMandatesCallTest( + fixtures.mitConfirmBody, + data, + 7000, + true, + "automatic", + globalState + ); + }); + } + ); + + context( + "Card - NoThreeDS Create + Confirm Manual CIT and MIT payment flow test", + () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Confirm No 3DS CIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MandateMultiUseNo3DSManualCapture"]; + + cy.citForMandatesCallTest( + fixtures.citConfirmBody, + data, + 6500, + true, + "manual", + "new_mandate", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("cit-capture-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Confirm No 3DS MIT 1", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITManualCapture"]; + + cy.mitForMandatesCallTest( + fixtures.mitConfirmBody, + data, + 6500, + true, + "manual", + globalState + ); + }); + + it("mit-capture-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Confirm No 3DS MIT 2", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITManualCapture"]; + + cy.mitForMandatesCallTest( + fixtures.mitConfirmBody, + data, + 6500, + true, + "manual", + globalState + ); + }); + + it("mit-capture-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + } + ); + + context( + "Card - ThreeDS Create + Confirm Manual CIT and MIT payment flow test", + () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Confirm No 3DS CIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MandateMultiUseNo3DSManualCapture"]; + + cy.citForMandatesCallTest( + fixtures.citConfirmBody, + data, + 6500, + true, + "manual", + "new_mandate", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("cit-capture-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + + cy.mitForMandatesCallTest( + fixtures.mitConfirmBody, + data, + 6500, + true, + "automatic", + globalState + ); + }); + } + ); +}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00012-ListAndRevokeMandate.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00012-ListAndRevokeMandate.cy.js deleted file mode 100644 index 9091135d5396..000000000000 --- a/cypress-tests/cypress/e2e/PaymentTest/00012-ListAndRevokeMandate.cy.js +++ /dev/null @@ -1,74 +0,0 @@ -import * as fixtures from "../../fixtures/imports"; -import State from "../../utils/State"; -import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; - -let globalState; - -describe("Card - SingleUse Mandates flow test", () => { - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - after("flush global state", () => { - cy.task("setGlobalState", globalState.data); - }); - - 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 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( - fixtures.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", () => { - cy.mitForMandatesCallTest( - fixtures.mitConfirmBody, - 7000, - true, - "automatic", - globalState - ); - }); - - it("list-mandate-call-test", () => { - cy.listMandateCallTest(globalState); - }); - - it("revoke-mandate-call-test", () => { - cy.revokeMandateCallTest(globalState); - }); - - it("revoke-revoked-mandate-call-test", () => { - cy.revokeMandateCallTest(globalState); - }); - } - ); -}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00013-ListAndRevokeMandate.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00013-ListAndRevokeMandate.cy.js new file mode 100644 index 000000000000..b8d0a51879de --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentTest/00013-ListAndRevokeMandate.cy.js @@ -0,0 +1,130 @@ +import * as fixtures from "../../fixtures/imports"; +import State from "../../utils/State"; +import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; + +let globalState; + +describe("Card - List and revoke Mandates flow test", () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + context( + "Card - NoThreeDS Create + Confirm Automatic CIT and MIT payment flow test", + () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Confirm No 3DS CIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MandateSingleUseNo3DSAutoCapture"]; + + cy.citForMandatesCallTest( + fixtures.citConfirmBody, + data, + 7000, + true, + "automatic", + "new_mandate", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + + cy.mitForMandatesCallTest( + fixtures.mitConfirmBody, + data, + 7000, + true, + "automatic", + globalState + ); + }); + + it("list-mandate-call-test", () => { + cy.listMandateCallTest(globalState); + }); + + it("revoke-mandate-call-test", () => { + cy.revokeMandateCallTest(globalState); + }); + + it("revoke-revoked-mandate-call-test", () => { + cy.revokeMandateCallTest(globalState); + }); + } + ); + context("Card - Zero auth CIT and MIT payment flow test", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Confirm No 3DS CIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["ZeroAuthMandate"]; + + cy.citForMandatesCallTest( + fixtures.citConfirmBody, + data, + 0, + true, + "automatic", + "setup_mandate", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("list-mandate-call-test", () => { + cy.listMandateCallTest(globalState); + }); + + it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + + cy.mitForMandatesCallTest( + fixtures.mitConfirmBody, + data, + 7000, + true, + "automatic", + globalState + ); + }); + + it("list-mandate-call-test", () => { + cy.listMandateCallTest(globalState); + }); + + it("revoke-mandate-call-test", () => { + cy.revokeMandateCallTest(globalState); + }); + }); +}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00013-SaveCardFlow.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00013-SaveCardFlow.cy.js deleted file mode 100644 index 6e9289d4d0b0..000000000000 --- a/cypress-tests/cypress/e2e/PaymentTest/00013-SaveCardFlow.cy.js +++ /dev/null @@ -1,491 +0,0 @@ -import * as fixtures from "../../fixtures/imports"; -import State from "../../utils/State"; -import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; - -let globalState; -let saveCardBody; - -describe("Card - SaveCard payment flow test", () => { - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - after("flush global state", () => { - cy.task("setGlobalState", globalState.data); - }); - - context( - "Save card for NoThreeDS automatic capture payment- Create+Confirm [on_session]", - () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails - - beforeEach(function () { - saveCardBody = Cypress._.cloneDeep(fixtures.saveCardConfirmBody); - if (!should_continue) { - this.skip(); - } - }); - - it("customer-create-call-test", () => { - cy.createCustomerCallTest(fixtures.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( - fixtures.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( - fixtures.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( - saveCardBody, - 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 [on_session]", - () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails - - beforeEach(function () { - saveCardBody = Cypress._.cloneDeep(fixtures.saveCardConfirmBody); - if (!should_continue) { - this.skip(); - } - }); - - it("customer-create-call-test", () => { - cy.createCustomerCallTest(fixtures.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( - fixtures.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( - fixtures.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 data = getConnectorDetails(globalState.get("connectorId"))[ - "card_pm" - ]["SaveCardUseNo3DSManualCapture"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.saveCardConfirmCallTest( - saveCardBody, - 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( - fixtures.captureBody, - req_data, - res_data, - 6500, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - } - ); - - context( - "Save card for NoThreeDS manual partial capture payment- Create + Confirm [on_session]", - () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails - - beforeEach(function () { - saveCardBody = Cypress._.cloneDeep(fixtures.saveCardConfirmBody); - if (!should_continue) { - this.skip(); - } - }); - - it("customer-create-call-test", () => { - cy.createCustomerCallTest(fixtures.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( - fixtures.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( - fixtures.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 data = getConnectorDetails(globalState.get("connectorId"))[ - "card_pm" - ]["SaveCardUseNo3DSManualCapture"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.saveCardConfirmCallTest( - saveCardBody, - 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( - fixtures.captureBody, - req_data, - res_data, - 100, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - } - ); - - context( - "Save card for NoThreeDS automatic capture payment [off_session]", - () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails - - beforeEach(function () { - saveCardBody = Cypress._.cloneDeep(fixtures.saveCardConfirmBody); - if (!should_continue) { - this.skip(); - } - }); - - it("customer-create-call-test", () => { - cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); - }); - - it("create+confirm-payment-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ - "card_pm" - ]["SaveCardUseNo3DSAutoCaptureOffSession"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createConfirmPaymentTest( - fixtures.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" - ]["PaymentIntentOffSession"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createPaymentIntentTest( - fixtures.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" - ]["SaveCardConfirmAutoCaptureOffSession"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - - cy.saveCardConfirmCallTest( - saveCardBody, - req_data, - res_data, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - } - ); - - context( - "Save card for NoThreeDS manual capture payment- Create+Confirm [off_session]", - () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails - - beforeEach(function () { - if (!should_continue) { - this.skip(); - } - saveCardBody = Cypress._.cloneDeep(fixtures.saveCardConfirmBody); - }); - - it("customer-create-call-test", () => { - cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); - }); - - it("create+confirm-payment-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ - "card_pm" - ]["SaveCardUseNo3DSManualCaptureOffSession"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createConfirmPaymentTest( - fixtures.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", () => { - 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( - fixtures.captureBody, - req_data, - res_data, - 6500, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - - it("retrieve-customerPM-call-test", () => { - cy.listCustomerPMCallTest(globalState); - }); - - it("create-payment-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ - "card_pm" - ]["PaymentIntentOffSession"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createPaymentIntentTest( - fixtures.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 data = getConnectorDetails(globalState.get("connectorId"))[ - "card_pm" - ]["SaveCardConfirmManualCaptureOffSession"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.saveCardConfirmCallTest( - saveCardBody, - 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( - fixtures.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); - }); - } - ); -}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00014-SaveCardFlow.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00014-SaveCardFlow.cy.js new file mode 100644 index 000000000000..bab24076f550 --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentTest/00014-SaveCardFlow.cy.js @@ -0,0 +1,631 @@ +import * as fixtures from "../../fixtures/imports"; +import State from "../../utils/State"; +import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; + +let globalState; +let saveCardBody; + +describe("Card - SaveCard payment flow test", () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + context( + "Save card for NoThreeDS automatic capture payment- Create+Confirm [on_session]", + () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + saveCardBody = Cypress._.cloneDeep(fixtures.saveCardConfirmBody); + if (!shouldContinue) { + this.skip(); + } + }); + + it("customer-create-call-test", () => { + cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); + }); + + it("create+confirm-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardUseNo3DSAutoCapture"]; + + cy.createConfirmPaymentTest( + fixtures.createConfirmPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) { + shouldContinue = utils.should_continue_further(data); + } + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardUseNo3DSAutoCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("retrieve-customerPM-call-test", () => { + cy.listCustomerPMCallTest(globalState); + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("confirm-save-card-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardUseNo3DSAutoCapture"]; + + cy.saveCardConfirmCallTest(saveCardBody, data, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + } + ); + + context( + "Save card for NoThreeDS manual full capture payment- Create+Confirm [on_session]", + () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + saveCardBody = Cypress._.cloneDeep(fixtures.saveCardConfirmBody); + if (!shouldContinue) { + this.skip(); + } + }); + + it("customer-create-call-test", () => { + cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); + }); + + it("create+confirm-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardUseNo3DSAutoCapture"]; + + cy.createConfirmPaymentTest( + fixtures.createConfirmPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardUseNo3DSAutoCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("retrieve-customerPM-call-test", () => { + cy.listCustomerPMCallTest(globalState); + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "manual", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("confirm-save-card-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardUseNo3DSManualCapture"]; + + cy.saveCardConfirmCallTest(saveCardBody, data, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardUseNo3DSManualCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("capture-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + } + ); + + context( + "Save card for NoThreeDS manual partial capture payment- Create + Confirm [on_session]", + () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + saveCardBody = Cypress._.cloneDeep(fixtures.saveCardConfirmBody); + if (!shouldContinue) { + this.skip(); + } + }); + + it("customer-create-call-test", () => { + cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); + }); + + it("create+confirm-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardUseNo3DSAutoCapture"]; + + cy.createConfirmPaymentTest( + fixtures.createConfirmPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardUseNo3DSAutoCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("retrieve-customerPM-call-test", () => { + cy.listCustomerPMCallTest(globalState); + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "manual", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("confirm-save-card-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardUseNo3DSManualCapture"]; + + cy.saveCardConfirmCallTest(saveCardBody, data, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardUseNo3DSManualCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("capture-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PartialCapture"]; + + cy.captureCallTest(fixtures.captureBody, data, 100, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + } + ); + + context( + "Save card for NoThreeDS automatic capture payment [off_session]", + () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + saveCardBody = Cypress._.cloneDeep(fixtures.saveCardConfirmBody); + if (!shouldContinue) { + this.skip(); + } + }); + + it("customer-create-call-test", () => { + cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); + }); + + it("create+confirm-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardUseNo3DSAutoCaptureOffSession"]; + + cy.createConfirmPaymentTest( + fixtures.createConfirmPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardUseNo3DSAutoCaptureOffSession"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("retrieve-customerPM-call-test", () => { + cy.listCustomerPMCallTest(globalState); + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("confirm-save-card-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardConfirmAutoCaptureOffSession"]; + + cy.saveCardConfirmCallTest(saveCardBody, data, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + } + ); + + context( + "Save card for NoThreeDS manual capture payment- Create+Confirm [off_session]", + () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + saveCardBody = Cypress._.cloneDeep(fixtures.saveCardConfirmBody); + }); + + it("customer-create-call-test", () => { + cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); + }); + + it("create+confirm-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardUseNo3DSManualCaptureOffSession"]; + + cy.createConfirmPaymentTest( + fixtures.createConfirmPaymentBody, + data, + "no_three_ds", + "manual", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardUseNo3DSManualCaptureOffSession"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + it("capture-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-customerPM-call-test", () => { + cy.listCustomerPMCallTest(globalState); + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "manual", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("confirm-save-card-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardConfirmManualCaptureOffSession"]; + + cy.saveCardConfirmCallTest(saveCardBody, data, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardConfirmManualCaptureOffSession"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("capture-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + } + ); + + context( + "Save card for NoThreeDS automatic capture payment - create and confirm [off_session]", + () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + saveCardBody = Cypress._.cloneDeep(fixtures.saveCardConfirmBody); + if (!shouldContinue) { + this.skip(); + } + }); + + it("customer-create-call-test", () => { + cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("confirm-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardUseNo3DSAutoCaptureOffSession"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardUseNo3DSAutoCaptureOffSession"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("retrieve-customerPM-call-test", () => { + cy.listCustomerPMCallTest(globalState); + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("confirm-save-card-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardConfirmAutoCaptureOffSession"]; + + cy.saveCardConfirmCallTest(saveCardBody, data, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + } + ); + context( + "Use billing address from payment method during subsequent payment[off_session]", + () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + saveCardBody = Cypress._.cloneDeep(fixtures.saveCardConfirmBody); + if (!shouldContinue) { + this.skip(); + } + }); + + it("customer-create-call-test", () => { + cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("confirm-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardUseNo3DSAutoCaptureOffSession"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-customerPM-call-test", () => { + cy.listCustomerPMCallTest(globalState); + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("confirm-save-card-payment-call-test-without-billing", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardConfirmAutoCaptureOffSessionWithoutBilling"]; + + cy.saveCardConfirmCallTest(saveCardBody, data, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + } + ); +}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00014-ZeroAuthMandate.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00014-ZeroAuthMandate.cy.js deleted file mode 100644 index a6e085658c64..000000000000 --- a/cypress-tests/cypress/e2e/PaymentTest/00014-ZeroAuthMandate.cy.js +++ /dev/null @@ -1,111 +0,0 @@ -import * as fixtures from "../../fixtures/imports"; -import State from "../../utils/State"; -import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; - -let globalState; - -describe("Card - SingleUse Mandates flow test", () => { - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - after("flush global state", () => { - cy.task("setGlobalState", globalState.data); - }); - - 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 data = getConnectorDetails(globalState.get("connectorId"))[ - "card_pm" - ]["ZeroAuthMandate"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.citForMandatesCallTest( - fixtures.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", () => { - cy.mitForMandatesCallTest( - fixtures.mitConfirmBody, - 7000, - true, - "automatic", - globalState - ); - }); - } - ); - 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 data = getConnectorDetails(globalState.get("connectorId"))[ - "card_pm" - ]["ZeroAuthMandate"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.citForMandatesCallTest( - fixtures.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", () => { - cy.mitForMandatesCallTest( - fixtures.mitConfirmBody, - 7000, - true, - "automatic", - globalState - ); - }); - it("Confirm No 3DS MIT", () => { - cy.mitForMandatesCallTest( - fixtures.mitConfirmBody, - 7000, - true, - "automatic", - globalState - ); - }); - } - ); -}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00015-ThreeDSManualCapture.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00015-ThreeDSManualCapture.cy.js deleted file mode 100644 index 3c9fca1428f4..000000000000 --- a/cypress-tests/cypress/e2e/PaymentTest/00015-ThreeDSManualCapture.cy.js +++ /dev/null @@ -1,277 +0,0 @@ -import captureBody from "../../fixtures/capture-flow-body.json"; -import * as fixtures from "../../fixtures/imports"; -import State from "../../utils/State"; -import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; - -let globalState; - -describe("Card - ThreeDS Manual payment flow test", () => { - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - afterEach("flush global state", () => { - cy.task("setGlobalState", globalState.data); - }); - - 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 data = getConnectorDetails(globalState.get("connectorId"))[ - "card_pm" - ]["PaymentIntent"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createPaymentIntentTest( - fixtures.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-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ - "card_pm" - ]["3DSManualCapture"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.confirmCallTest( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - - it("Handle redirection", () => { - let expected_redirection = fixtures.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); - }); - }); - - 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 data = getConnectorDetails(globalState.get("connectorId"))[ - "card_pm" - ]["3DSManualCapture"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createConfirmPaymentTest( - fixtures.createConfirmPaymentBody, - req_data, - res_data, - "three_ds", - "manual", - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - - it("Handle redirection", () => { - let expected_redirection = - fixtures.createConfirmPaymentBody["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); - }); - }); - }); - - 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 data = getConnectorDetails(globalState.get("connectorId"))[ - "card_pm" - ]["PaymentIntent"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createPaymentIntentTest( - fixtures.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-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ - "card_pm" - ]["3DSManualCapture"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.confirmCallTest( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - - it("Handle redirection", () => { - let expected_redirection = fixtures.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); - }); - }); - - 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 data = getConnectorDetails(globalState.get("connectorId"))[ - "card_pm" - ]["3DSManualCapture"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createConfirmPaymentTest( - fixtures.createConfirmPaymentBody, - req_data, - res_data, - "three_ds", - "manual", - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - - it("Handle redirection", () => { - let expected_redirection = - fixtures.createConfirmPaymentBody["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); - }); - }); - } - ); -}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00015-ZeroAuthMandate.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00015-ZeroAuthMandate.cy.js new file mode 100644 index 000000000000..0cc1be503189 --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentTest/00015-ZeroAuthMandate.cy.js @@ -0,0 +1,202 @@ +import * as fixtures from "../../fixtures/imports"; +import State from "../../utils/State"; +import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; + +let globalState; + +describe("Card - SingleUse Mandates flow test", () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + context( + "Card - NoThreeDS Create + Confirm Automatic CIT and Single use MIT payment flow test", + () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Confirm No 3DS CIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["ZeroAuthMandate"]; + + cy.citForMandatesCallTest( + fixtures.citConfirmBody, + data, + 0, + true, + "automatic", + "setup_mandate", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + + cy.mitForMandatesCallTest( + fixtures.mitConfirmBody, + data, + 7000, + true, + "automatic", + globalState + ); + }); + } + ); + context( + "Card - NoThreeDS Create + Confirm Automatic CIT and Multi use MIT payment flow test", + () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Confirm No 3DS CIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["ZeroAuthMandate"]; + + cy.citForMandatesCallTest( + fixtures.citConfirmBody, + data, + 0, + true, + "automatic", + "setup_mandate", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + + cy.mitForMandatesCallTest( + fixtures.mitConfirmBody, + data, + 7000, + true, + "automatic", + globalState + ); + }); + it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + + cy.mitForMandatesCallTest( + fixtures.mitConfirmBody, + data, + 7000, + true, + "automatic", + globalState + ); + }); + } + ); + + context("Card - Zero Auth Payment", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Create No 3DS Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["ZeroAuthPaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Confirm No 3DS payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["ZeroAuthConfirmPayment"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Retrieve Payment Call Test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["ZeroAuthConfirmPayment"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("Retrieve CustomerPM Call Test", () => { + cy.listCustomerPMCallTest(globalState); + }); + + it("Create Recurring Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Confirm Recurring Payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardConfirmAutoCaptureOffSession"]; + + cy.saveCardConfirmCallTest( + fixtures.saveCardConfirmBody, + data, + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); +}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00016-ThreeDSManualCapture.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00016-ThreeDSManualCapture.cy.js new file mode 100644 index 000000000000..0426d17013ce --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentTest/00016-ThreeDSManualCapture.cy.js @@ -0,0 +1,293 @@ +import captureBody from "../../fixtures/capture-flow-body.json"; +import * as fixtures from "../../fixtures/imports"; +import State from "../../utils/State"; +import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; + +let globalState; + +describe("Card - ThreeDS Manual payment flow test", () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + afterEach("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + context("Card - ThreeDS Manual Full Capture payment flow test", () => { + context("payment Create and Confirm", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "three_ds", + "manual", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("confirm-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSManualCapture"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Handle redirection", () => { + const expected_redirection = fixtures.confirmBody["return_url"]; + cy.handleRedirection(globalState, expected_redirection); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSManualCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("capture-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.captureCallTest(captureBody, data, 6500, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + }); + + context("Payment Create+Confirm", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create+confirm-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSManualCapture"]; + + cy.createConfirmPaymentTest( + fixtures.createConfirmPaymentBody, + data, + "three_ds", + "manual", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Handle redirection", () => { + const expected_redirection = + fixtures.createConfirmPaymentBody["return_url"]; + cy.handleRedirection(globalState, expected_redirection); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSManualCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("capture-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.captureCallTest(captureBody, data, 6500, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + }); + }); + + context( + "Card - ThreeDS Manual Partial Capture payment flow test - Create and Confirm", + () => { + context("payment Create and Payment Confirm", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "three_ds", + "manual", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("confirm-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSManualCapture"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Handle redirection", () => { + const expected_redirection = fixtures.confirmBody["return_url"]; + cy.handleRedirection(globalState, expected_redirection); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSManualCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("capture-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PartialCapture"]; + + cy.captureCallTest(captureBody, data, 100, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PartialCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + }); + + context("payment + Confirm", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create+confirm-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSManualCapture"]; + + cy.createConfirmPaymentTest( + fixtures.createConfirmPaymentBody, + data, + "three_ds", + "manual", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Handle redirection", () => { + const expected_redirection = + fixtures.createConfirmPaymentBody["return_url"]; + cy.handleRedirection(globalState, expected_redirection); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSManualCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("capture-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PartialCapture"]; + + cy.captureCallTest(captureBody, data, 100, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PartialCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + }); + } + ); +}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00017-BankRedirect.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00017-BankRedirect.cy.js deleted file mode 100644 index 85a63ca008c5..000000000000 --- a/cypress-tests/cypress/e2e/PaymentTest/00017-BankRedirect.cy.js +++ /dev/null @@ -1,392 +0,0 @@ -import * as fixtures from "../../fixtures/imports"; -import State from "../../utils/State"; -import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; - -let globalState; - -describe("Bank Redirect tests", () => { - afterEach("flush global state", () => { - cy.task("setGlobalState", globalState.data); - }); - - context("Blik Create and Confirm flow test", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails - - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - beforeEach(function () { - if (!should_continue) { - this.skip(); - } - }); - - it("create-payment-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ - "bank_redirect_pm" - ]["BlikPaymentIntent"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createPaymentIntentTest( - fixtures.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 bank redirect", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ - "bank_redirect_pm" - ]["Blik"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.confirmBankRedirectCallTest( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - }); - - context("EPS Create and Confirm flow test", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails - - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - beforeEach(function () { - if (!should_continue) { - this.skip(); - } - }); - - it("create-payment-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ - "bank_redirect_pm" - ]["PaymentIntent"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createPaymentIntentTest( - fixtures.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 bank redirect", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ - "bank_redirect_pm" - ]["Eps"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.confirmBankRedirectCallTest( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - - it("Handle bank redirect redirection", () => { - // return_url is a static url (https://hyperswitch.io) taken from confirm-body fixture and is not updated - let expected_redirection = fixtures.confirmBody["return_url"]; - let payment_method_type = globalState.get("paymentMethodType"); - cy.handleBankRedirectRedirection( - globalState, - payment_method_type, - expected_redirection - ); - }); - }); - - context("iDEAL Create and Confirm flow test", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails - - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - beforeEach(function () { - if (!should_continue) { - this.skip(); - } - }); - - it("create-payment-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ - "bank_redirect_pm" - ]["PaymentIntent"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createPaymentIntentTest( - fixtures.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 bank redirect", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ - "bank_redirect_pm" - ]["Ideal"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.confirmBankRedirectCallTest( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - - it("Handle bank redirect redirection", () => { - // return_url is a static url (https://hyperswitch.io) taken from confirm-body fixture and is not updated - let expected_redirection = fixtures.confirmBody["return_url"]; - let payment_method_type = globalState.get("paymentMethodType"); - cy.handleBankRedirectRedirection( - globalState, - payment_method_type, - expected_redirection - ); - }); - }); - - context("Giropay Create and Confirm flow test", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails - - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - beforeEach(function () { - if (!should_continue) { - this.skip(); - } - }); - it("create-payment-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ - "bank_redirect_pm" - ]["PaymentIntent"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createPaymentIntentTest( - fixtures.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 bank redirect", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ - "bank_redirect_pm" - ]["Giropay"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.confirmBankRedirectCallTest( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - - it("Handle bank redirect redirection", () => { - // return_url is a static url (https://hyperswitch.io) taken from confirm-body fixture and is not updated - let expected_redirection = fixtures.confirmBody["return_url"]; - let payment_method_type = globalState.get("paymentMethodType"); - cy.handleBankRedirectRedirection( - globalState, - payment_method_type, - expected_redirection - ); - }); - }); - - context("Sofort Create and Confirm flow test", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails - - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - beforeEach(function () { - if (!should_continue) { - this.skip(); - } - }); - it("create-payment-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ - "bank_redirect_pm" - ]["PaymentIntent"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createPaymentIntentTest( - fixtures.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 bank redirect", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ - "bank_redirect_pm" - ]["Sofort"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.confirmBankRedirectCallTest( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - - it("Handle bank redirect redirection", () => { - // return_url is a static url (https://hyperswitch.io) taken from confirm-body fixture and is not updated - let expected_redirection = fixtures.confirmBody["return_url"]; - let payment_method_type = globalState.get("paymentMethodType"); - cy.handleBankRedirectRedirection( - globalState, - payment_method_type, - expected_redirection - ); - }); - }); - - context("Przelewy24 Create and Confirm flow test", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails - - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - beforeEach(function () { - if (!should_continue) { - this.skip(); - } - }); - it("create-payment-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ - "bank_redirect_pm" - ]["PaymentIntent"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createPaymentIntentTest( - fixtures.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 bank redirect", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ - "bank_redirect_pm" - ]["Przelewy24"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.confirmBankRedirectCallTest( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - - it("Handle bank redirect redirection", () => { - let expected_redirection = fixtures.confirmBody["return_url"]; - let payment_method_type = globalState.get("paymentMethodType"); - cy.handleBankRedirectRedirection( - globalState, - payment_method_type, - expected_redirection - ); - }); - }); -}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00016-BankTransfers.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00017-BankTransfers.cy.js similarity index 61% rename from cypress-tests/cypress/e2e/PaymentTest/00016-BankTransfers.cy.js rename to cypress-tests/cypress/e2e/PaymentTest/00017-BankTransfers.cy.js index ee83f1aa4485..913dd91ea8be 100644 --- a/cypress-tests/cypress/e2e/PaymentTest/00016-BankTransfers.cy.js +++ b/cypress-tests/cypress/e2e/PaymentTest/00017-BankTransfers.cy.js @@ -16,30 +16,28 @@ describe("Bank Transfers", () => { }); context("Bank transfer - Pix forward flow", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails beforeEach(function () { - if (!should_continue) { + if (!shouldContinue) { this.skip(); } }); it("create-payment-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ + const data = getConnectorDetails(globalState.get("connectorId"))[ "bank_transfer_pm" ]["PaymentIntent"]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createPaymentIntentTest( fixtures.createPaymentBody, - req_data, - res_data, + data, "three_ds", "automatic", globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); it("payment_methods-call-test", () => { @@ -47,25 +45,24 @@ describe("Bank Transfers", () => { }); it("Confirm bank transfer", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ + const data = getConnectorDetails(globalState.get("connectorId"))[ "bank_transfer_pm" ]["Pix"]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.confirmBankTransferCallTest( fixtures.confirmBody, - req_data, - res_data, + data, true, globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); it("Handle bank transfer redirection", () => { - let expected_redirection = fixtures.confirmBody["return_url"]; - let payment_method_type = globalState.get("paymentMethodType"); + const expected_redirection = fixtures.confirmBody["return_url"]; + const payment_method_type = globalState.get("paymentMethodType"); + cy.handleBankTransferRedirection( globalState, payment_method_type, diff --git a/cypress-tests/cypress/e2e/PaymentTest/00018-BankRedirect.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00018-BankRedirect.cy.js new file mode 100644 index 000000000000..cc3b8dbaec76 --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentTest/00018-BankRedirect.cy.js @@ -0,0 +1,303 @@ +import * as fixtures from "../../fixtures/imports"; +import State from "../../utils/State"; +import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; + +let globalState; + +describe("Bank Redirect tests", () => { + afterEach("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + context("Blik Create and Confirm flow test", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "bank_redirect_pm" + ]["BlikPaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "three_ds", + "automatic", + globalState + ); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("Confirm bank redirect", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "bank_redirect_pm" + ]["Blik"]; + + cy.confirmBankRedirectCallTest( + fixtures.confirmBody, + data, + true, + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); + + context("EPS Create and Confirm flow test", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "bank_redirect_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "three_ds", + "automatic", + globalState + ); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("Confirm bank redirect", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "bank_redirect_pm" + ]["Eps"]; + + cy.confirmBankRedirectCallTest( + fixtures.confirmBody, + data, + true, + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Handle bank redirect redirection", () => { + // return_url is a static url (https://hyperswitch.io) taken from confirm-body fixture and is not updated + const expected_redirection = fixtures.confirmBody["return_url"]; + const payment_method_type = globalState.get("paymentMethodType"); + + cy.handleBankRedirectRedirection( + globalState, + payment_method_type, + expected_redirection + ); + }); + }); + + context("iDEAL Create and Confirm flow test", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "bank_redirect_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "three_ds", + "automatic", + globalState + ); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("Confirm bank redirect", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "bank_redirect_pm" + ]["Ideal"]; + + cy.confirmBankRedirectCallTest( + fixtures.confirmBody, + data, + true, + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Handle bank redirect redirection", () => { + // return_url is a static url (https://hyperswitch.io) taken from confirm-body fixture and is not updated + const expected_redirection = fixtures.confirmBody["return_url"]; + const payment_method_type = globalState.get("paymentMethodType"); + cy.handleBankRedirectRedirection( + globalState, + payment_method_type, + expected_redirection + ); + }); + }); + + context("Sofort Create and Confirm flow test", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "bank_redirect_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "three_ds", + "automatic", + globalState + ); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("Confirm bank redirect", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "bank_redirect_pm" + ]["Sofort"]; + + cy.confirmBankRedirectCallTest( + fixtures.confirmBody, + data, + true, + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Handle bank redirect redirection", () => { + // return_url is a static url (https://hyperswitch.io) taken from confirm-body fixture and is not updated + const expected_redirection = fixtures.confirmBody["return_url"]; + const payment_method_type = globalState.get("paymentMethodType"); + cy.handleBankRedirectRedirection( + globalState, + payment_method_type, + expected_redirection + ); + }); + }); + + context("Przelewy24 Create and Confirm flow test", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "bank_redirect_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "three_ds", + "automatic", + globalState + ); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + + it("Confirm bank redirect", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "bank_redirect_pm" + ]["Przelewy24"]; + + cy.confirmBankRedirectCallTest( + fixtures.confirmBody, + data, + true, + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Handle bank redirect redirection", () => { + const expected_redirection = fixtures.confirmBody["return_url"]; + const payment_method_type = globalState.get("paymentMethodType"); + cy.handleBankRedirectRedirection( + globalState, + payment_method_type, + expected_redirection + ); + }); + }); +}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00018-MandatesUsingPMID.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00019-MandatesUsingPMID.cy.js similarity index 53% rename from cypress-tests/cypress/e2e/PaymentTest/00018-MandatesUsingPMID.cy.js rename to cypress-tests/cypress/e2e/PaymentTest/00019-MandatesUsingPMID.cy.js index b2951e375862..1a2dff1b5da3 100644 --- a/cypress-tests/cypress/e2e/PaymentTest/00018-MandatesUsingPMID.cy.js +++ b/cypress-tests/cypress/e2e/PaymentTest/00019-MandatesUsingPMID.cy.js @@ -18,57 +18,58 @@ describe("Card - Mandates using Payment Method Id flow test", () => { context( "Card - NoThreeDS Create and Confirm Automatic CIT and MIT payment flow test", () => { - let should_continue = true; + let shouldContinue = true; beforeEach(function () { - if (!should_continue) { + if (!shouldContinue) { this.skip(); } }); it("Create No 3DS Payment Intent", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ + const data = getConnectorDetails(globalState.get("connectorId"))[ "card_pm" ]["PaymentIntent"]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createPaymentIntentTest( fixtures.createPaymentBody, - req_data, - res_data, + data, "no_three_ds", "automatic", globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); }); it("Confirm No 3DS CIT", () => { - console.log("confirm -> " + globalState.get("connectorId")); - let data = getConnectorDetails(globalState.get("connectorId"))[ + const data = getConnectorDetails(globalState.get("connectorId"))[ "card_pm" ]["PaymentMethodIdMandateNo3DSAutoCapture"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - console.log("det -> " + data.card); + cy.citForMandatesCallTest( fixtures.citConfirmBody, - req_data, - res_data, + data, 7000, true, "automatic", "new_mandate", globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); }); it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + cy.mitUsingPMId( fixtures.pmIdConfirmBody, + data, 7000, true, "automatic", @@ -81,75 +82,69 @@ describe("Card - Mandates using Payment Method Id flow test", () => { context( "Card - NoThreeDS Create and Confirm Manual CIT and MIT payment flow test", () => { - let should_continue = true; + let shouldContinue = true; beforeEach(function () { - if (!should_continue) { + if (!shouldContinue) { this.skip(); } }); it("Create No 3DS Payment Intent", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ + const data = getConnectorDetails(globalState.get("connectorId"))[ "card_pm" ]["PaymentIntent"]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createPaymentIntentTest( fixtures.createPaymentBody, - req_data, - res_data, + data, "no_three_ds", "manual", globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); }); it("Confirm No 3DS CIT", () => { - console.log("confirm -> " + globalState.get("connectorId")); - let data = getConnectorDetails(globalState.get("connectorId"))[ + const data = getConnectorDetails(globalState.get("connectorId"))[ "card_pm" ]["PaymentMethodIdMandateNo3DSManualCapture"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - console.log("det -> " + data.card); + cy.citForMandatesCallTest( fixtures.citConfirmBody, - req_data, - res_data, + data, 6500, true, "manual", "new_mandate", globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); }); it("cit-capture-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ + const 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( - fixtures.captureBody, - req_data, - res_data, - 6500, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); }); it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + cy.mitUsingPMId( fixtures.pmIdConfirmBody, + data, 7000, true, "automatic", @@ -162,39 +157,41 @@ describe("Card - Mandates using Payment Method Id flow test", () => { context( "Card - NoThreeDS Create + Confirm Automatic CIT and MIT payment flow test", () => { - let should_continue = true; + let shouldContinue = true; beforeEach(function () { - if (!should_continue) { + if (!shouldContinue) { this.skip(); } }); it("Confirm No 3DS CIT", () => { - console.log("confirm -> " + globalState.get("connectorId")); - let data = getConnectorDetails(globalState.get("connectorId"))[ + const data = getConnectorDetails(globalState.get("connectorId"))[ "card_pm" ]["PaymentMethodIdMandateNo3DSAutoCapture"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - console.log("det -> " + data.card); + cy.citForMandatesCallTest( fixtures.citConfirmBody, - req_data, - res_data, + data, 7000, true, "automatic", "new_mandate", globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); }); it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + cy.mitUsingPMId( fixtures.pmIdConfirmBody, + data, 7000, true, "automatic", @@ -202,8 +199,13 @@ describe("Card - Mandates using Payment Method Id flow test", () => { ); }); it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + cy.mitUsingPMId( fixtures.pmIdConfirmBody, + data, 7000, true, "automatic", @@ -216,57 +218,52 @@ describe("Card - Mandates using Payment Method Id flow test", () => { context( "Card - NoThreeDS Create + Confirm Manual CIT and MIT payment flow test", () => { - let should_continue = true; + let shouldContinue = true; beforeEach(function () { - if (!should_continue) { + if (!shouldContinue) { this.skip(); } }); it("Confirm No 3DS CIT", () => { - console.log("confirm -> " + globalState.get("connectorId")); - let data = getConnectorDetails(globalState.get("connectorId"))[ + const data = getConnectorDetails(globalState.get("connectorId"))[ "card_pm" ]["PaymentMethodIdMandateNo3DSManualCapture"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - console.log("det -> " + data.card); + cy.citForMandatesCallTest( fixtures.citConfirmBody, - req_data, - res_data, + data, 6500, true, "manual", "new_mandate", globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); }); it("cit-capture-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ + const 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( - fixtures.captureBody, - req_data, - res_data, - 6500, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); }); it("Confirm No 3DS MIT 1", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITManualCapture"]; + cy.mitUsingPMId( fixtures.pmIdConfirmBody, + data, 6500, true, "manual", @@ -275,26 +272,24 @@ describe("Card - Mandates using Payment Method Id flow test", () => { }); it("mit-capture-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ + const 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( - fixtures.captureBody, - req_data, - res_data, - 6500, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); }); it("Confirm No 3DS MIT 2", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITManualCapture"]; + cy.mitUsingPMId( fixtures.pmIdConfirmBody, + data, 6500, true, "manual", @@ -303,21 +298,14 @@ describe("Card - Mandates using Payment Method Id flow test", () => { }); it("mit-capture-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ + const 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( - fixtures.captureBody, - req_data, - res_data, - 6500, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); }); } ); @@ -325,44 +313,46 @@ describe("Card - Mandates using Payment Method Id flow test", () => { context( "Card - ThreeDS Create + Confirm Automatic CIT and MIT payment flow test", () => { - let should_continue = true; + let shouldContinue = true; beforeEach(function () { - if (!should_continue) { + if (!shouldContinue) { this.skip(); } }); it("Confirm 3DS CIT", () => { - console.log("confirm -> " + globalState.get("connectorId")); - let data = getConnectorDetails(globalState.get("connectorId"))[ + const data = getConnectorDetails(globalState.get("connectorId"))[ "card_pm" ]["PaymentMethodIdMandate3DSAutoCapture"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - console.log("det -> " + data.card); + cy.citForMandatesCallTest( fixtures.citConfirmBody, - req_data, - res_data, + data, 7000, true, "automatic", "new_mandate", globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); }); it("Handle redirection", () => { - let expected_redirection = fixtures.citConfirmBody["return_url"]; + const expected_redirection = fixtures.citConfirmBody["return_url"]; cy.handleRedirection(globalState, expected_redirection); }); it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + cy.mitUsingPMId( fixtures.pmIdConfirmBody, + data, 7000, true, "automatic", @@ -370,8 +360,13 @@ describe("Card - Mandates using Payment Method Id flow test", () => { ); }); it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + cy.mitUsingPMId( fixtures.pmIdConfirmBody, + data, 7000, true, "automatic", @@ -384,62 +379,57 @@ describe("Card - Mandates using Payment Method Id flow test", () => { context( "Card - ThreeDS Create + Confirm Manual CIT and MIT payment flow", () => { - let should_continue = true; + let shouldContinue = true; beforeEach(function () { - if (!should_continue) { + if (!shouldContinue) { this.skip(); } }); it("Confirm 3DS CIT", () => { - console.log("confirm -> " + globalState.get("connectorId")); - let data = getConnectorDetails(globalState.get("connectorId"))[ + const data = getConnectorDetails(globalState.get("connectorId"))[ "card_pm" ]["PaymentMethodIdMandate3DSManualCapture"]; - let req_data = data["Request"]; - let res_data = data["Response"]; cy.citForMandatesCallTest( fixtures.citConfirmBody, - req_data, - res_data, + data, 6500, true, "manual", "new_mandate", globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); }); it("Handle redirection", () => { - let expected_redirection = fixtures.citConfirmBody["return_url"]; + const expected_redirection = fixtures.citConfirmBody["return_url"]; cy.handleRedirection(globalState, expected_redirection); }); it("cit-capture-call-test", () => { - let data = getConnectorDetails(globalState.get("connectorId"))[ + const 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( - fixtures.captureBody, - req_data, - res_data, - 6500, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); }); it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + cy.mitUsingPMId( fixtures.pmIdConfirmBody, + data, 7000, true, "automatic", diff --git a/cypress-tests/cypress/e2e/PaymentTest/00020-MandatesUsingNTIDProxy.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00020-MandatesUsingNTIDProxy.cy.js new file mode 100644 index 000000000000..e3080d39878e --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentTest/00020-MandatesUsingNTIDProxy.cy.js @@ -0,0 +1,242 @@ +import * as fixtures from "../../fixtures/imports"; +import State from "../../utils/State"; +import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; + +let globalState; +let connector; + +describe("Card - Mandates using Network Transaction Id flow test", () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + connector = globalState.get("connectorId"); + }); + }); + + afterEach("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + context( + "Card - NoThreeDS Create and Confirm Automatic MIT payment flow test", + () => { + beforeEach(function () { + if (connector !== "cybersource") { + this.skip(); + } + }); + + it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + + cy.mitUsingNTID( + fixtures.ntidConfirmBody, + data, + 7000, + true, + "automatic", + globalState + ); + }); + } + ); + + context( + "Card - NoThreeDS Create and Confirm Manual MIT payment flow test", + () => { + beforeEach(function () { + if (connector !== "cybersource") { + this.skip(); + } + }); + + it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITManualCapture"]; + + cy.mitUsingNTID( + fixtures.ntidConfirmBody, + data, + 7000, + true, + "manual", + globalState + ); + }); + } + ); + + context( + "Card - NoThreeDS Create and Confirm Automatic multiple MITs payment flow test", + () => { + beforeEach(function () { + if (connector !== "cybersource") { + this.skip(); + } + }); + + it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + + cy.mitUsingNTID( + fixtures.ntidConfirmBody, + data, + 7000, + true, + "automatic", + globalState + ); + }); + it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + + cy.mitUsingNTID( + fixtures.ntidConfirmBody, + data, + 7000, + true, + "automatic", + globalState + ); + }); + } + ); + + context( + "Card - NoThreeDS Create and Confirm Manual multiple MITs payment flow test", + () => { + let shouldContinue = true; + + beforeEach(function () { + if (connector !== "cybersource") { + this.skip(); + } + }); + + it("Confirm No 3DS MIT 1", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITManualCapture"]; + + cy.mitUsingNTID( + fixtures.ntidConfirmBody, + data, + 6500, + true, + "manual", + globalState + ); + }); + + it("mit-capture-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Confirm No 3DS MIT 2", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITManualCapture"]; + + cy.mitUsingNTID( + fixtures.ntidConfirmBody, + data, + 6500, + true, + "manual", + globalState + ); + }); + + it("mit-capture-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + } + ); + + context( + "Card - ThreeDS Create and Confirm Automatic multiple MITs payment flow test", + () => { + beforeEach(function () { + if (connector !== "cybersource") { + this.skip(); + } + }); + + it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + + cy.mitUsingNTID( + fixtures.ntidConfirmBody, + data, + 7000, + true, + "automatic", + globalState + ); + }); + it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + + cy.mitUsingNTID( + fixtures.ntidConfirmBody, + data, + 7000, + true, + "automatic", + globalState + ); + }); + } + ); + + context( + "Card - ThreeDS Create and Confirm Manual multiple MITs payment flow", + () => { + beforeEach(function () { + if (connector !== "cybersource") { + this.skip(); + } + }); + + it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + + cy.mitUsingNTID( + fixtures.ntidConfirmBody, + data, + 7000, + true, + "automatic", + globalState + ); + }); + } + ); +}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00020-Variations.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00020-Variations.cy.js deleted file mode 100644 index a00a2e39f959..000000000000 --- a/cypress-tests/cypress/e2e/PaymentTest/00020-Variations.cy.js +++ /dev/null @@ -1,746 +0,0 @@ -import * as fixtures from "../../fixtures/imports"; -import State from "../../utils/State"; -import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; - -let globalState; -let paymentIntentBody; -let paymentCreateConfirmBody; - -describe("Corner cases", () => { - // This is needed to get flush out old data - beforeEach("seed global state", () => { - paymentIntentBody = Cypress._.cloneDeep(fixtures.createPaymentBody); - paymentCreateConfirmBody = Cypress._.cloneDeep( - fixtures.createConfirmPaymentBody - ); - }); - - context("[Payment] Invalid Info", () => { - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - after("flush global state", () => { - cy.task("setGlobalState", globalState.data); - }); - - it("[Payment] Invalid card number", () => { - let data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "InvalidCardNumber" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - - cy.createConfirmPaymentTest( - paymentIntentBody, - req_data, - res_data, - "three_ds", - "automatic", - globalState - ); - }); - - it("[Payment] Invalid expiry month", () => { - let data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "InvalidExpiryMonth" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - - cy.createConfirmPaymentTest( - paymentIntentBody, - req_data, - res_data, - "three_ds", - "automatic", - globalState - ); - }); - - it("[Payment] Invalid expiry year", () => { - let data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "InvalidExpiryYear" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - - cy.createConfirmPaymentTest( - paymentIntentBody, - req_data, - res_data, - "three_ds", - "automatic", - globalState - ); - }); - - it("[Payment] Invalid card CVV", () => { - let data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "InvalidCardCvv" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - - cy.createConfirmPaymentTest( - paymentIntentBody, - req_data, - res_data, - "three_ds", - "automatic", - globalState - ); - }); - - it("[Payment] Invalid currency", () => { - let data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "InvalidCurrency" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - - cy.createConfirmPaymentTest( - paymentIntentBody, - req_data, - res_data, - "three_ds", - "automatic", - globalState - ); - }); - - it("[Payment] Invalid capture method", () => { - let data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "InvalidCaptureMethod" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - - cy.createConfirmPaymentTest( - paymentIntentBody, - req_data, - res_data, - "three_ds", - "automatic", - globalState - ); - }); - - it("[Payment] Invalid payment method", () => { - let data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "InvalidPaymentMethod" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - - cy.createConfirmPaymentTest( - paymentIntentBody, - req_data, - res_data, - "three_ds", - "automatic", - globalState - ); - }); - - it("[Payment] Invalid `amount_to_capture`", () => { - let data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "InvalidAmountToCapture" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - - cy.createConfirmPaymentTest( - paymentIntentBody, - req_data, - res_data, - "three_ds", - "automatic", - globalState - ); - }); - - it("[Payment] Missing required params", () => { - let data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "MissingRequiredParam" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - - cy.createConfirmPaymentTest( - paymentIntentBody, - req_data, - res_data, - "three_ds", - "automatic", - globalState - ); - }); - }); - - context("[Payment] Confirm w/o PMD", () => { - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - after("flush global state", () => { - cy.task("setGlobalState", globalState.data); - }); - - it("Create payment intent", () => { - let data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "PaymentIntent" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - - cy.createPaymentIntentTest( - paymentIntentBody, - req_data, - res_data, - "no_three_ds", - "automatic", - globalState - ); - }); - - it("Confirm payment intent", () => { - let data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "PaymentIntentErrored" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - - cy.confirmCallTest( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); - }); - }); - - context("[Payment] Capture greater amount", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails - - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - after("flush global state", () => { - cy.task("setGlobalState", globalState.data); - }); - - beforeEach(function () { - if (!should_continue) { - this.skip(); - } - }); - - it("Create payment intent and confirm", () => { - let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ - "No3DSManualCapture" - ]; - - let req_data = data["Request"]; - let res_data = data["Response"]; - - cy.createConfirmPaymentTest( - paymentCreateConfirmBody, - req_data, - res_data, - "no_three_ds", - "manual", - globalState - ); - - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - - it("Capture call", () => { - let data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "CaptureGreaterAmount" - ]; - - let req_data = data["Request"]; - let res_data = data["Response"]; - - cy.captureCallTest( - fixtures.captureBody, - req_data, - res_data, - 65000, - globalState - ); - - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - }); - - context("[Payment] Capture successful payment", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails - - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - after("flush global state", () => { - cy.task("setGlobalState", globalState.data); - }); - - beforeEach(function () { - if (!should_continue) { - this.skip(); - } - }); - - it("Create payment intent and confirm", () => { - let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ - "No3DSAutoCapture" - ]; - - let req_data = data["Request"]; - let res_data = data["Response"]; - - cy.createConfirmPaymentTest( - paymentCreateConfirmBody, - req_data, - res_data, - "no_three_ds", - "automatic", - globalState - ); - - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - - it("Retrieve payment", () => { - cy.retrievePaymentCallTest(globalState); - }); - - it("Capture call", () => { - let data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "CaptureCapturedAmount" - ]; - - let req_data = data["Request"]; - let res_data = data["Response"]; - - cy.captureCallTest( - fixtures.captureBody, - req_data, - res_data, - 65000, - globalState - ); - - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - }); - - context("[Payment] Confirm successful payment", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails - - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - after("flush global state", () => { - cy.task("setGlobalState", globalState.data); - }); - - beforeEach(function () { - if (!should_continue) { - this.skip(); - } - }); - - it("Create payment intent and confirm", () => { - let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ - "No3DSAutoCapture" - ]; - - let req_data = data["Request"]; - let res_data = data["Response"]; - - cy.createConfirmPaymentTest( - paymentCreateConfirmBody, - req_data, - res_data, - "no_three_ds", - "automatic", - globalState - ); - - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - - it("Retrieve payment", () => { - cy.retrievePaymentCallTest(globalState); - }); - - it("Confirm call", () => { - let data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "ConfirmSuccessfulPayment" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - - cy.confirmCallTest( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); - - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - }); - - context("[Payment] Void successful payment", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails - - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - after("flush global state", () => { - cy.task("setGlobalState", globalState.data); - }); - - beforeEach(function () { - if (!should_continue) { - this.skip(); - } - }); - - it("Create payment intent and confirm", () => { - let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ - "No3DSAutoCapture" - ]; - - let req_data = data["Request"]; - let res_data = data["Response"]; - - cy.createConfirmPaymentTest( - paymentCreateConfirmBody, - req_data, - res_data, - "no_three_ds", - "automatic", - globalState - ); - - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - - it("Retrieve payment", () => { - cy.retrievePaymentCallTest(globalState); - }); - - it("Void call", () => { - // `commons` here is intentionally used as we need to pass `ResponseCustom` - let data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "Void" - ]; - let req_data = data["Request"]; - let res_data = data["ResponseCustom"]; - cy.voidCallTest(fixtures.voidBody, req_data, res_data, globalState); - - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - }); - - context("[Payment] 3DS with greater capture", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails - - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - afterEach("flush global state", () => { - cy.task("setGlobalState", globalState.data); - }); - - beforeEach(function () { - if (!should_continue) { - this.skip(); - } - }); - - it("Create payment intent and confirm", () => { - let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ - "3DSManualCapture" - ]; - - let req_data = data["Request"]; - let res_data = data["Response"]; - - cy.createConfirmPaymentTest( - paymentCreateConfirmBody, - req_data, - res_data, - "three_ds", - "manual", - globalState - ); - - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - - it("Retrieve payment", () => { - cy.retrievePaymentCallTest(globalState); - }); - - it("Handle redirection", () => { - let expected_redirection = fixtures.confirmBody["return_url"]; - cy.handleRedirection(globalState, expected_redirection); - }); - - it("Retrieve payment", () => { - cy.retrievePaymentCallTest(globalState); - }); - - it("Capture call", () => { - let data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "CaptureGreaterAmount" - ]; - - let req_data = data["Request"]; - let res_data = data["Response"]; - - cy.captureCallTest( - fixtures.captureBody, - req_data, - res_data, - 65000, - globalState - ); - - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - }); - - context("[Payment] Refund exceeds captured Amount", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails - - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - after("flush global state", () => { - cy.task("setGlobalState", globalState.data); - }); - - beforeEach(function () { - if (!should_continue) { - this.skip(); - } - }); - - it("Create payment intent and confirm", () => { - let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ - "No3DSAutoCapture" - ]; - - let req_data = data["Request"]; - let res_data = data["Response"]; - - cy.createConfirmPaymentTest( - paymentCreateConfirmBody, - req_data, - res_data, - "no_three_ds", - "automatic", - globalState - ); - - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - - it("Retrieve payment", () => { - cy.retrievePaymentCallTest(globalState); - }); - - it("Refund call", () => { - // `commons` here is intentionally used as we need to pass `ResponseCustom` - let data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "Refund" - ]; - let req_data = data["Request"]; - let res_data = data["ResponseCustom"]; - cy.refundCallTest( - fixtures.refundBody, - req_data, - res_data, - 65000, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - }); - - context("[Payment] Refund unsuccessful payment", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails - - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - after("flush global state", () => { - cy.task("setGlobalState", globalState.data); - }); - - beforeEach(function () { - if (!should_continue) { - this.skip(); - } - }); - - it("Create payment intent and confirm", () => { - let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ - "No3DSAutoCapture" - ]; - - let req_data = data["Request"]; - let res_data = data["Response"]; - - cy.createConfirmPaymentTest( - paymentCreateConfirmBody, - req_data, - res_data, - "no_three_ds", - "automatic", - globalState - ); - - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - - it("Retrieve payment", () => { - cy.retrievePaymentCallTest(globalState); - }); - - it("Refund call", () => { - // `commons` here is intentionally used as we need to pass `ResponseCustom` - let data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "Refund" - ]; - let req_data = data["Request"]; - let res_data = data["ResponseCustom"]; - cy.refundCallTest( - fixtures.refundBody, - req_data, - res_data, - 65000, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - }); - - context("[Payment] Recurring mandate with greater mandate amount", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails - - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); - }); - - after("flush global state", () => { - cy.task("setGlobalState", globalState.data); - }); - - beforeEach(function () { - if (!should_continue) { - this.skip(); - } - }); - - it("No 3DS CIT", () => { - let data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ - "MandateSingleUseNo3DSManualCapture" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.citForMandatesCallTest( - fixtures.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 data = getConnectorDetails(globalState.get("connectorId"))["card_pm"][ - "Capture" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.captureCallTest( - fixtures.captureBody, - req_data, - res_data, - 6500, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); - }); - - it("Retrieve payment", () => { - cy.retrievePaymentCallTest(globalState); - }); - - it("Confirm No 3DS MIT", () => { - cy.mitForMandatesCallTest( - fixtures.mitConfirmBody, - 65000, - true, - "manual", - globalState - ); - }); - }); -}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00019-UPI.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00021-UPI.cy.js similarity index 52% rename from cypress-tests/cypress/e2e/PaymentTest/00019-UPI.cy.js rename to cypress-tests/cypress/e2e/PaymentTest/00021-UPI.cy.js index 72c99d2d19d1..e7a06c622489 100644 --- a/cypress-tests/cypress/e2e/PaymentTest/00019-UPI.cy.js +++ b/cypress-tests/cypress/e2e/PaymentTest/00021-UPI.cy.js @@ -5,7 +5,7 @@ import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; let globalState; describe("UPI Payments - Hyperswitch", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails context("[Payment] [UPI - UPI Collect] Create & Confirm + Refund", () => { before("seed global state", () => { @@ -19,29 +19,25 @@ describe("UPI Payments - Hyperswitch", () => { }); beforeEach(function () { - if (!should_continue) { + if (!shouldContinue) { this.skip(); } }); it("Create payment intent", () => { - let data = getConnectorDetails(globalState.get("connectorId"))["upi_pm"][ - "PaymentIntent" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; + const data = getConnectorDetails(globalState.get("connectorId"))[ + "upi_pm" + ]["PaymentIntent"]; cy.createPaymentIntentTest( fixtures.createPaymentBody, - req_data, - res_data, + data, "three_ds", "automatic", globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); it("List Merchant payment methods", () => { @@ -49,27 +45,19 @@ describe("UPI Payments - Hyperswitch", () => { }); it("Confirm payment", () => { - let data = getConnectorDetails(globalState.get("connectorId"))["upi_pm"][ - "UpiCollect" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - - cy.confirmUpiCall( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); + const data = getConnectorDetails(globalState.get("connectorId"))[ + "upi_pm" + ]["UpiCollect"]; - if (should_continue) - should_continue = utils.should_continue_further(res_data); + cy.confirmUpiCall(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); it("Handle UPI Redirection", () => { - let expected_redirection = fixtures.confirmBody["return_url"]; - let payment_method_type = globalState.get("paymentMethodType"); + const expected_redirection = fixtures.confirmBody["return_url"]; + const payment_method_type = globalState.get("paymentMethodType"); + cy.handleUpiRedirection( globalState, payment_method_type, @@ -78,31 +66,27 @@ describe("UPI Payments - Hyperswitch", () => { }); it("Retrieve payment", () => { - cy.retrievePaymentCallTest(globalState); + const data = getConnectorDetails(globalState.get("connectorId"))[ + "upi_pm" + ]["UpiCollect"]; + + cy.retrievePaymentCallTest(globalState, data); }); it("Refund payment", () => { - let data = getConnectorDetails(globalState.get("connectorId"))["upi_pm"][ - "Refund" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.refundCallTest( - fixtures.refundBody, - req_data, - res_data, - 6500, - globalState - ); + const data = getConnectorDetails(globalState.get("connectorId"))[ + "upi_pm" + ]["Refund"]; - if (should_continue) - should_continue = utils.should_continue_further(res_data); + cy.refundCallTest(fixtures.refundBody, data, 6500, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); }); // Skipping UPI Intent intentionally as connector is throwing 5xx during redirection context.skip("[Payment] [UPI - UPI Intent] Create & Confirm", () => { - should_continue = true; // variable that will be used to skip tests if a previous test fails + shouldContinue = true; // variable that will be used to skip tests if a previous test fails before("seed global state", () => { cy.task("getGlobalState").then((state) => { @@ -115,29 +99,25 @@ describe("UPI Payments - Hyperswitch", () => { }); beforeEach(function () { - if (!should_continue) { + if (!shouldContinue) { this.skip(); } }); it("Create payment intent", () => { - let data = getConnectorDetails(globalState.get("connectorId"))["upi_pm"][ - "PaymentIntent" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; + const data = getConnectorDetails(globalState.get("connectorId"))[ + "upi_pm" + ]["PaymentIntent"]; cy.createPaymentIntentTest( fixtures.createPaymentBody, - req_data, - res_data, + data, "three_ds", "automatic", globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); it("List Merchant payment methods", () => { @@ -145,27 +125,19 @@ describe("UPI Payments - Hyperswitch", () => { }); it("Confirm payment", () => { - let data = getConnectorDetails(globalState.get("connectorId"))["upi_pm"][ - "UpiIntent" - ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - - cy.confirmUpiCall( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); + const data = getConnectorDetails(globalState.get("connectorId"))[ + "upi_pm" + ]["UpiIntent"]; - if (should_continue) - should_continue = utils.should_continue_further(res_data); + cy.confirmUpiCall(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); it("Handle UPI Redirection", () => { - let expected_redirection = fixtures.confirmBody["return_url"]; - let payment_method_type = globalState.get("paymentMethodType"); + const expected_redirection = fixtures.confirmBody["return_url"]; + const payment_method_type = globalState.get("paymentMethodType"); + cy.handleUpiRedirection( globalState, payment_method_type, @@ -174,7 +146,11 @@ describe("UPI Payments - Hyperswitch", () => { }); it("Retrieve payment", () => { - cy.retrievePaymentCallTest(globalState); + const data = getConnectorDetails(globalState.get("connectorId"))[ + "upi_pm" + ]["UpiIntent"]; + + cy.retrievePaymentCallTest(globalState, data); }); }); }); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00022-Variations.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00022-Variations.cy.js new file mode 100644 index 000000000000..be594e764871 --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentTest/00022-Variations.cy.js @@ -0,0 +1,724 @@ +import * as fixtures from "../../fixtures/imports"; +import State from "../../utils/State"; +import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; + +let globalState; +let paymentIntentBody; +let paymentCreateConfirmBody; + +describe("Corner cases", () => { + // This is needed to get flush out old data + beforeEach("seed global state", () => { + paymentIntentBody = Cypress._.cloneDeep(fixtures.createPaymentBody); + paymentCreateConfirmBody = Cypress._.cloneDeep( + fixtures.createConfirmPaymentBody + ); + }); + + context("[Payment] Invalid Info", () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + it("[Payment] Invalid card number", () => { + const data = getConnectorDetails(globalState.get("commons"))["card_pm"][ + "InvalidCardNumber" + ]; + + cy.createConfirmPaymentTest( + paymentIntentBody, + data, + "three_ds", + "automatic", + globalState + ); + }); + + it("[Payment] Invalid expiry month", () => { + const data = getConnectorDetails(globalState.get("commons"))["card_pm"][ + "InvalidExpiryMonth" + ]; + + cy.createConfirmPaymentTest( + paymentIntentBody, + data, + "three_ds", + "automatic", + globalState + ); + }); + + it("[Payment] Invalid expiry year", () => { + const data = getConnectorDetails(globalState.get("commons"))["card_pm"][ + "InvalidExpiryYear" + ]; + + cy.createConfirmPaymentTest( + paymentIntentBody, + data, + "three_ds", + "automatic", + globalState + ); + }); + + it("[Payment] Invalid card CVV", () => { + const data = getConnectorDetails(globalState.get("commons"))["card_pm"][ + "InvalidCardCvv" + ]; + + cy.createConfirmPaymentTest( + paymentIntentBody, + data, + "three_ds", + "automatic", + globalState + ); + }); + + it("[Payment] Invalid currency", () => { + const data = getConnectorDetails(globalState.get("commons"))["card_pm"][ + "InvalidCurrency" + ]; + + cy.createConfirmPaymentTest( + paymentIntentBody, + data, + "three_ds", + "automatic", + globalState + ); + }); + + it("[Payment] Invalid capture method", () => { + const data = getConnectorDetails(globalState.get("commons"))["card_pm"][ + "InvalidCaptureMethod" + ]; + + cy.createConfirmPaymentTest( + paymentIntentBody, + data, + "three_ds", + "automatic", + globalState + ); + }); + + it("[Payment] Invalid payment method", () => { + const data = getConnectorDetails(globalState.get("commons"))["card_pm"][ + "InvalidPaymentMethod" + ]; + + cy.createConfirmPaymentTest( + paymentIntentBody, + data, + "three_ds", + "automatic", + globalState + ); + }); + + it("[Payment] Invalid `amount_to_capture`", () => { + const data = getConnectorDetails(globalState.get("commons"))["card_pm"][ + "InvalidAmountToCapture" + ]; + + cy.createConfirmPaymentTest( + paymentIntentBody, + data, + "three_ds", + "automatic", + globalState + ); + }); + + it("[Payment] Missing required params", () => { + const data = getConnectorDetails(globalState.get("commons"))["card_pm"][ + "MissingRequiredParam" + ]; + + cy.createConfirmPaymentTest( + paymentIntentBody, + data, + "three_ds", + "automatic", + globalState + ); + }); + }); + + context("[Payment] Confirm w/o PMD", () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + it("Create payment intent", () => { + const data = getConnectorDetails(globalState.get("commons"))["card_pm"][ + "PaymentIntent" + ]; + + cy.createPaymentIntentTest( + paymentIntentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + }); + + it("Confirm payment intent", () => { + const data = getConnectorDetails(globalState.get("commons"))["card_pm"][ + "PaymentIntentErrored" + ]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + }); + }); + + context("[Payment] Capture greater amount", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Create payment intent and confirm", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSManualCapture"]; + + cy.createConfirmPaymentTest( + paymentCreateConfirmBody, + data, + "no_three_ds", + "manual", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Capture call", () => { + const data = getConnectorDetails(globalState.get("commons"))["card_pm"][ + "CaptureGreaterAmount" + ]; + + cy.captureCallTest(fixtures.captureBody, data, 65000, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); + + context("[Payment] Capture successful payment", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Create payment intent and confirm", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.createConfirmPaymentTest( + paymentCreateConfirmBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Retrieve payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("Capture call", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["CaptureCapturedAmount"]; + + cy.captureCallTest(fixtures.captureBody, data, 65000, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); + + context("[Payment] Confirm successful payment", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Create payment intent and confirm", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.createConfirmPaymentTest( + paymentCreateConfirmBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Retrieve payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("Confirm call", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["ConfirmSuccessfulPayment"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); + + context("[Payment] Void successful payment", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Create payment intent and confirm", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.createConfirmPaymentTest( + paymentCreateConfirmBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Retrieve payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("Void call", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Void"]; + const commonData = getConnectorDetails(globalState.get("commons"))[ + "card_pm" + ]["Void"]; + + const newData = { + ...data, + Response: utils.getConnectorFlowDetails( + data, + commonData, + "ResponseCustom" + ), + }; + + cy.voidCallTest(fixtures.voidBody, newData, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); + + context("[Payment] 3DS with greater capture", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + afterEach("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Create payment intent and confirm", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSManualCapture"]; + + cy.createConfirmPaymentTest( + paymentCreateConfirmBody, + data, + "three_ds", + "manual", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Retrieve payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSManualCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("Handle redirection", () => { + const expected_redirection = fixtures.confirmBody["return_url"]; + cy.handleRedirection(globalState, expected_redirection); + }); + + it("Retrieve payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSManualCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("Capture call", () => { + const data = getConnectorDetails(globalState.get("commons"))["card_pm"][ + "CaptureGreaterAmount" + ]; + + cy.captureCallTest(fixtures.captureBody, data, 65000, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); + + context("[Payment] Refund exceeds captured Amount", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Create payment intent and confirm", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.createConfirmPaymentTest( + paymentCreateConfirmBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Retrieve payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("Refund call", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Refund"]; + const commonData = getConnectorDetails(globalState.get("commons"))[ + "card_pm" + ]["Refund"]; + + const newData = { + ...data, + Response: utils.getConnectorFlowDetails( + data, + commonData, + "ResponseCustom" + ), + }; + + cy.refundCallTest(fixtures.refundBody, newData, 65000, globalState); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); + + context("[Payment] Refund unsuccessful payment", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Create payment intent and confirm", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.createConfirmPaymentTest( + paymentCreateConfirmBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Retrieve payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("Refund call", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Refund"]; + const commonData = getConnectorDetails(globalState.get("commons"))[ + "card_pm" + ]["Refund"]; + + const newData = { + ...data, + Response: utils.getConnectorFlowDetails( + data, + commonData, + "ResponseCustom" + ), + }; + + cy.refundCallTest(fixtures.refundBody, newData, 65000, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); + + context("[Payment] Recurring mandate with greater mandate amount", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("No 3DS CIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MandateSingleUseNo3DSManualCapture"]; + + cy.citForMandatesCallTest( + fixtures.citConfirmBody, + data, + 6500, + true, + "manual", + "new_mandate", + globalState + ); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("cit-capture-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Retrieve payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("Confirm No 3DS MIT", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + + cy.mitForMandatesCallTest( + fixtures.mitConfirmBody, + data, + 65000, + true, + "manual", + globalState + ); + }); + }); + context("Card-NoThreeDS fail payment flow test", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Confirm No 3DS", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSFailPayment"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("retrieve-payment-call-test", () => { + cy.retrievePaymentCallTest(globalState); + }); + }); +}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00023-PaymentMethods.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00023-PaymentMethods.cy.js new file mode 100644 index 000000000000..28262d025c4d --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentTest/00023-PaymentMethods.cy.js @@ -0,0 +1,104 @@ +import * as fixtures from "../../fixtures/imports"; +import State from "../../utils/State"; +import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; + +let globalState; + +describe("Payment Methods Tests", () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + context("Create payment method for customer", () => { + it("Create customer", () => { + cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); + }); + + it("Create Payment Method", () => { + const data = getConnectorDetails("commons")["card_pm"]["PaymentMethod"]; + + cy.createPaymentMethodTest(globalState, data); + }); + + it("List PM for customer", () => { + cy.listCustomerPMCallTest(globalState); + }); + }); + + context("Set default payment method", () => { + let shouldContinue = true; + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("List PM for customer", () => { + cy.listCustomerPMCallTest(globalState); + }); + + it("Create Payment Method", () => { + const data = getConnectorDetails("commons")["card_pm"]["PaymentMethod"]; + + cy.createPaymentMethodTest(globalState, data); + }); + + it("create-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("confirm-payment-call-test", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardUseNo3DSAutoCaptureOffSession"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("List PM for customer", () => { + cy.listCustomerPMCallTest(globalState); + }); + + it("Set default payment method", () => { + cy.setDefaultPaymentMethodTest(globalState); + }); + }); + + context("Delete payment method for customer", () => { + it("Create customer", () => { + cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); + }); + + it("Create Payment Method", () => { + const data = getConnectorDetails("commons")["card_pm"]["PaymentMethod"]; + cy.createPaymentMethodTest(globalState, data); + }); + + it("List PM for customer", () => { + cy.listCustomerPMCallTest(globalState); + }); + + it("Delete Payment Method for a customer", () => { + cy.deletePaymentMethodTest(globalState); + }); + }); +}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00024-ConnectorAgnosticNTID.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00024-ConnectorAgnosticNTID.cy.js new file mode 100644 index 000000000000..db5757e464cb --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentTest/00024-ConnectorAgnosticNTID.cy.js @@ -0,0 +1,400 @@ +import * as fixtures from "../../fixtures/imports"; +import State from "../../utils/State"; +import { payment_methods_enabled } from "../PaymentUtils/Commons"; +import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; + +let globalState; + +/* +Flow: +- Create Business Profile with connector agnostic feature disabled +- Create Merchant Connector Account and Customer +- Make a Payment +- List Payment Method for Customer using Client Secret (will get PMID) + +- Create Business Profile with connector agnostic feature enabled +- Create Merchant Connector Account +- Create Payment Intent +- List Payment Method for Customer -- Empty list; i.e., no payment method should be listed +- Confirm Payment with PMID from previous step (should fail as Connector Mandate ID is not present in the newly created Profile) + + +- Create Business Profile with connector agnostic feature enabled +- Create Merchant Connector Account and Customer +- Make a Payment +- List Payment Method for Customer using Client Secret (will get PMID) + +- Create Business Profile with connector agnostic feature enabled +- Create Merchant Connector Account +- Create Payment Intent +- List Payment Method for Customer using Client Secret (will get PMID which is same as the one from previous step along with Payment Token) +- Confirm Payment with PMID from previous step (should pass as NTID is present in the DB) +*/ + +describe("Connector Agnostic Tests", () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + context( + "Connector Agnostic Disabled for Profile 1 and Enabled for Profile 2", + () => { + let shouldContinue = true; + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Create business profile", () => { + utils.createBusinessProfile( + fixtures.businessProfile.bpCreate, + globalState + ); + }); + + it("Create merchant connector account", () => { + utils.createMerchantConnectorAccount( + "payment_processor", + fixtures.createConnectorBody, + globalState, + payment_methods_enabled + ); + }); + + it("Create Customer", () => { + cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Confirm Payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardUseNo3DSAutoCaptureOffSession"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("List Payment Method for Customer using Client Secret", () => { + cy.listCustomerPMByClientSecret(globalState); + }); + + it("Create business profile", () => { + utils.createBusinessProfile( + fixtures.businessProfile.bpCreate, + globalState + ); + }); + + it("Create merchant connector account", () => { + utils.createMerchantConnectorAccount( + "payment_processor", + fixtures.createConnectorBody, + globalState, + payment_methods_enabled + ); + }); + + it("Enable Connector Agnostic for Business Profile", () => { + utils.updateBusinessProfile( + fixtures.businessProfile.bpUpdate, + true, // is_connector_agnostic_enabled + false, // collect_billing_address_from_wallet_connector + false, // collect_shipping_address_from_wallet_connector + false, // always_collect_billing_address_from_wallet_connector + false, // always_collect_shipping_address_from_wallet_connector + globalState + ); + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("List Payment Method for Customer", () => { + cy.listCustomerPMByClientSecret(globalState); + }); + + it("Confirm No 3DS MIT (PMID)", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + const commonData = getConnectorDetails(globalState.get("commons"))[ + "card_pm" + ]["MITAutoCapture"]; + + const newData = { + ...data, + Response: utils.getConnectorFlowDetails( + data, + commonData, + "ResponseCustom" + ), + }; + + cy.mitUsingPMId( + fixtures.pmIdConfirmBody, + newData, + 7000, + true, + "automatic", + globalState + ); + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("List Payment Method for Customer", () => { + cy.listCustomerPMByClientSecret(globalState); + }); + + it("Confirm No 3DS MIT (Token)", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardConfirmAutoCaptureOffSession"]; + const commonData = getConnectorDetails(globalState.get("commons"))[ + "card_pm" + ]["SaveCardConfirmAutoCaptureOffSession"]; + + const newData = { + ...data, + Response: utils.getConnectorFlowDetails( + data, + commonData, + "ResponseCustom" + ), + }; + cy.saveCardConfirmCallTest( + fixtures.saveCardConfirmBody, + newData, + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + } + ); + + context("Connector Agnostic Enabled for Profile 1 and Profile 2", () => { + let shouldContinue = true; + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Create business profile", () => { + utils.createBusinessProfile( + fixtures.businessProfile.bpCreate, + globalState + ); + }); + + it("Create merchant connector account", () => { + utils.createMerchantConnectorAccount( + "payment_processor", + fixtures.createConnectorBody, + globalState, + payment_methods_enabled + ); + }); + + it("Create Customer", () => { + cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); + }); + + it("Enable Connector Agnostic for Business Profile", () => { + utils.updateBusinessProfile( + fixtures.businessProfile.bpUpdate, + true, // is_connector_agnostic_enabled + false, // collect_billing_address_from_wallet_connector + false, // collect_shipping_address_from_wallet_connector + false, // always_collect_billing_address_from_wallet_connector + false, // always_collect_shipping_address_from_wallet_connector + globalState + ); + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Confirm Payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardUseNo3DSAutoCaptureOffSession"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("List Payment Method for Customer using Client Secret", () => { + cy.listCustomerPMByClientSecret(globalState); + }); + + it("Create business profile", () => { + utils.createBusinessProfile( + fixtures.businessProfile.bpCreate, + globalState + ); + }); + + it("Create merchant connector account", () => { + utils.createMerchantConnectorAccount( + "payment_processor", + fixtures.createConnectorBody, + globalState, + payment_methods_enabled + ); + }); + + it("Enable Connector Agnostic for Business Profile", () => { + utils.updateBusinessProfile( + fixtures.businessProfile.bpUpdate, + true, // is_connector_agnostic_enabled + false, // collect_billing_address_from_wallet_connector + false, // collect_shipping_address_from_wallet_connector + false, // always_collect_billing_address_from_wallet_connector + false, // always_collect_shipping_address_from_wallet_connector + globalState + ); + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("List Payment Method for Customer", () => { + cy.listCustomerPMByClientSecret(globalState); + }); + + it("Confirm No 3DS MIT (PMID)", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + + cy.mitUsingPMId( + fixtures.pmIdConfirmBody, + data, + 7000, + true, + "automatic", + globalState + ); + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("List Payment Method for Customer", () => { + cy.listCustomerPMByClientSecret(globalState); + }); + + it("Confirm No 3DS MIT (Token)", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardConfirmAutoCaptureOffSession"]; + + cy.saveCardConfirmCallTest( + fixtures.saveCardConfirmBody, + data, + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); +}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00025-ConfigTest.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00025-ConfigTest.cy.js new file mode 100644 index 000000000000..b1ccc55ccb34 --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentTest/00025-ConfigTest.cy.js @@ -0,0 +1,379 @@ +import * as fixtures from "../../fixtures/imports"; +import State from "../../utils/State"; +import { payment_methods_enabled } from "../PaymentUtils/Commons"; +import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; + +let globalState; + +describe("Config Tests", () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + context( + "Update collect_billing_details_from_wallet_connector to true and verifying in payment method list, this config should be true", + () => { + let shouldContinue = true; + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Create Business Profile", () => { + cy.createBusinessProfileTest( + fixtures.businessProfile.bpCreate, + globalState + ); + }); + + it("connector-create-call-test", () => { + cy.createConnectorCallTest( + "payment_processor", + fixtures.createConnectorBody, + payment_methods_enabled, + globalState + ); + }); + + it("Create Customer", () => { + cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); + }); + + it("Update collect_billing_details_from_wallet_connector to true", () => { + cy.UpdateBusinessProfileTest( + fixtures.businessProfile.bpUpdate, + true, // is_connector_agnostic_enabled + true, // collect_billing_address_from_wallet_connector + false, // collect_shipping_address_from_wallet_connector + false, // always_collect_billing_address_from_wallet_connector + false, // always_collect_shipping_address_from_wallet_connector + globalState + ); + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + } + ); + + context( + "Update collect_shipping_details_from_wallet_connector to true and verifying in payment method list, this config should be true", + () => { + let shouldContinue = true; + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Update collect_shipping_details_from_wallet_connector to true", () => { + cy.UpdateBusinessProfileTest( + fixtures.businessProfile.bpUpdate, + true, // is_connector_agnostic_enabled + false, // collect_billing_address_from_wallet_connector + false, // collect_shipping_address_from_wallet_connector + false, // always_collect_billing_address_from_wallet_connector + false, // always_collect_shipping_address_from_wallet_connector + globalState + ); + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + } + ); + + context( + "Update always_collect_billing_details_from_wallet_connector to true and verifying in payment method list, this config should be true", + () => { + let shouldContinue = true; + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Update always_collect_billing_details_from_wallet_connector to true", () => { + cy.UpdateBusinessProfileTest( + fixtures.businessProfile.bpUpdate, + true, // is_connector_agnostic_enabled + false, // collect_billing_address_from_wallet_connector + false, // collect_shipping_address_from_wallet_connector + true, // always_collect_billing_address_from_wallet_connector + false, // always_collect_shipping_address_from_wallet_connector + globalState + ); + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + } + ); + + context( + "Update always_collect_shipping_details_from_wallet_connector to true and verifying in payment method list, this config should be true", + () => { + let shouldContinue = true; + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Update always_collect_shipping_details_from_wallet_connector to true", () => { + cy.UpdateBusinessProfileTest( + fixtures.businessProfile.bpUpdate, + true, // is_connector_agnostic_enabled + false, // collect_billing_address_from_wallet_connector + false, // collect_shipping_address_from_wallet_connector + false, // always_collect_billing_address_from_wallet_connector + true, // always_collect_shipping_address_from_wallet_connector + globalState + ); + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + } + ); + + context( + "Update always_collect_shipping_details_from_wallet_connector & collect_shipping_details_from_wallet_connector to true and verifying in payment method list, this config should be true", + () => { + let shouldContinue = true; + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Update both always & collect_shipping_details_from_wallet_connector to true", () => { + cy.UpdateBusinessProfileTest( + fixtures.businessProfile.bpUpdate, + true, // is_connector_agnostic_enabled + false, // collect_billing_address_from_wallet_connector + true, // collect_shipping_address_from_wallet_connector + false, // always_collect_billing_address_from_wallet_connector + true, // always_collect_shipping_address_from_wallet_connector + globalState + ); + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + } + ); + context( + "Update always_collect_billing_details_from_wallet_connector & to collect_billing_details_from_wallet_connector to true and verifying in payment method list, this config should be true", + () => { + let shouldContinue = true; + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Update both always & collect_billing_details_from_wallet_connector to true", () => { + cy.UpdateBusinessProfileTest( + fixtures.businessProfile.bpUpdate, + true, // is_connector_agnostic_enabled + true, // collect_billing_address_from_wallet_connector + false, // collect_shipping_address_from_wallet_connector + true, // always_collect_billing_address_from_wallet_connector + false, // always_collect_shipping_address_from_wallet_connector + globalState + ); + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + } + ); + + context( + "Update all config(Collect address config) to false and verifying in payment method list, both config should be false", + () => { + let shouldContinue = true; + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Create Business Profile", () => { + cy.createBusinessProfileTest( + fixtures.businessProfile.bpCreate, + globalState + ); + }); + + it("connector-create-call-test", () => { + cy.createConnectorCallTest( + "payment_processor", + fixtures.createConnectorBody, + payment_methods_enabled, + globalState + ); + }); + + it("Create Customer", () => { + cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); + }); + + it("Update all config to false", () => { + cy.UpdateBusinessProfileTest( + fixtures.businessProfile.bpUpdate, + true, // is_connector_agnostic_enabled + false, // collect_billing_address_from_wallet_connector + false, // collect_shipping_address_from_wallet_connector + false, // always_collect_billing_address_from_wallet_connector + false, // always_collect_shipping_address_from_wallet_connector + globalState + ); + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("payment_methods-call-test", () => { + cy.paymentMethodsCallTest(globalState); + }); + } + ); +}); diff --git a/cypress-tests/cypress/e2e/PaymentTest/00026-DynamicFields.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00026-DynamicFields.cy.js new file mode 100644 index 000000000000..a325ac5f2f0d --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentTest/00026-DynamicFields.cy.js @@ -0,0 +1,175 @@ +import * as fixtures from "../../fixtures/imports"; +import State from "../../utils/State"; +import { cardCreditEnabled } from "../PaymentMethodListUtils/Commons"; +import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; + +let globalState; + +describe("Dynamic Fields Verification", () => { + context("Verify the Dynamic fields for card", () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + context( + "Verify the Dynamic fields - Payment without billing address", + () => { + let shouldContinue = true; + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Create Business Profile", () => { + cy.createBusinessProfileTest( + fixtures.businessProfile.bpCreate, + globalState + ); + }); + + it("connector-create-call-test", () => { + cy.createConnectorCallTest( + "payment_processor", + fixtures.createConnectorBody, + cardCreditEnabled, + globalState + ); + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentWithoutBilling"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Payment Method List", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "pm_list" + ]["PmListResponse"]["pmListDynamicFieldWithoutBilling"]; + cy.paymentMethodListTestWithRequiredFields(data, globalState); + }); + } + ); + + context("Verify the Dynamic fields - Payment with billing address", () => { + let shouldContinue = true; + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentWithBilling"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Payment Method List", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "pm_list" + ]["PmListResponse"]["pmListDynamicFieldWithBilling"]; + cy.paymentMethodListTestWithRequiredFields(data, globalState); + }); + }); + + context( + "Verify the Dynamic fields - Payment with billing First and Last name", + () => { + let shouldContinue = true; + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentWithFullName"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Payment Method List", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "pm_list" + ]["PmListResponse"]["pmListDynamicFieldWithNames"]; + cy.paymentMethodListTestWithRequiredFields(data, globalState); + }); + } + ); + + context("Verify the Dynamic fields - Payment with billing Email", () => { + let shouldContinue = true; + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentWithBillingEmail"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Payment Method List", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "pm_list" + ]["PmListResponse"]["pmListDynamicFieldWithEmail"]; + cy.paymentMethodListTestWithRequiredFields(data, globalState); + }); + }); + }); +}); +1; diff --git a/cypress-tests/cypress/e2e/PaymentTest/00027-IncrementalAuth.cy.js b/cypress-tests/cypress/e2e/PaymentTest/00027-IncrementalAuth.cy.js new file mode 100644 index 000000000000..e281fc0a2f7a --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentTest/00027-IncrementalAuth.cy.js @@ -0,0 +1,139 @@ +import * as fixtures from "../../fixtures/imports"; +import State from "../../utils/State"; +import getConnectorDetails, * as utils from "../PaymentUtils/Utils"; + +let connector; +let globalState; + +describe.skip("[Payment] Incremental Auth", () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + connector = globalState.get("connectorId"); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + context("[Payment] Incremental Pre-Auth", () => { + let shouldContinue = true; + + beforeEach(function () { + if (!shouldContinue || connector !== "cybersource") { + this.skip(); + } + }); + + it("[Payment] Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + const newData = { + ...data, + Request: { + ...data.Request, + request_incremental_authorization: true, + }, + }; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + newData, + "no_three_ds", + "manual", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + it("[Payment] Confirm Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardUseNo3DSManualCaptureOffSession"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + it("[Payment] Incremental Authorization", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["IncrementalAuth"]; + cy.incrementalAuth(globalState, data); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + it("[Payment] Capture Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.captureCallTest(fixtures.captureBody, data, 7000, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); + + context("[Payment] [Saved Card] Incremental Pre-Auth", () => { + let shouldContinue = true; + + beforeEach(function () { + if (!shouldContinue || connector !== "cybersource") { + this.skip(); + } + }); + + it("[Payment] List customer payment methods", () => { + cy.listCustomerPMCallTest(globalState); + }); + it("[Payment] Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "manual", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + it("[Payment] Confirm Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardUseNo3DSManualCaptureOffSession"]; + + cy.saveCardConfirmCallTest( + fixtures.saveCardConfirmBody, + data, + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + it("[Payment] Incremental Authorization", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["IncrementalAuth"]; + + cy.incrementalAuth(globalState, data); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + it("[Payment] Capture Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.captureCallTest(fixtures.captureBody, data, 7000, globalState); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); +}); diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Adyen.js b/cypress-tests/cypress/e2e/PaymentUtils/Adyen.js index e39838d03a83..56efbf1f6f21 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Adyen.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Adyen.js @@ -16,6 +16,14 @@ const successfulThreeDSTestCardDetails = { card_cvc: "737", }; +const failedNo3DSCardDetails = { + card_number: "4242424242424242", + card_exp_month: "01", + card_exp_year: "25", + card_holder_name: "joseph Doe", + card_cvc: "123", +}; + const singleUseMandateData = { customer_acceptance: { acceptance_type: "offline", @@ -67,6 +75,8 @@ export const connectorDetails = { }, PaymentIntentOffSession: { Request: { + amount: 6500, + authentication_type: "no_three_ds", currency: "USD", customer_acceptance: null, setup_future_usage: "off_session", @@ -78,6 +88,40 @@ export const connectorDetails = { }, }, }, + PaymentIntentWithShippingCost: { + Request: { + currency: "USD", + shipping_cost: 50, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + shipping_cost: 50, + amount: 6500, + }, + }, + }, + PaymentConfirmWithShippingCost: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + shipping_cost: 50, + amount_received: 6550, + amount: 6500, + net_amount: 6550, + }, + }, + }, "3DSManualCapture": { Request: { payment_method: "card", @@ -91,7 +135,7 @@ export const connectorDetails = { Response: { status: 200, body: { - status: "processing", + status: "requires_customer_action", }, }, }, @@ -146,6 +190,26 @@ export const connectorDetails = { }, }, }, + No3DSFailPayment: { + Request: { + payment_method: "card", + payment_method_data: { + card: failedNo3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "failed", + error_code: "2", + error_message: "Refused", + unified_code: "UE_9000", + unified_message: "Something went wrong", + }, + }, + }, Capture: { Request: { payment_method: "card", @@ -178,7 +242,7 @@ export const connectorDetails = { }, }, }, - Void: { + VoidAfterConfirm: getCustomExchange({ Request: {}, Response: { status: 200, @@ -186,7 +250,13 @@ export const connectorDetails = { status: "processing", }, }, - }, + ResponseCustom: { + status: 200, + body: { + status: "cancelled", + }, + }, + }), Refund: { Request: { currency: "USD", @@ -209,6 +279,48 @@ export const connectorDetails = { }, }, }, + manualPaymentRefund: { + Request: { + payment_method: "card", + payment_method_data: { + 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", + }, + }, + }, + }, + manualPaymentPartialRefund: { + Request: { + payment_method: "card", + payment_method_data: { + 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", + }, + }, + }, + }, SyncRefund: { Request: { currency: "USD", @@ -348,6 +460,24 @@ export const connectorDetails = { }, }, }, + MITAutoCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MITManualCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, ZeroAuthMandate: { Request: { payment_method: "card", @@ -364,6 +494,37 @@ export const connectorDetails = { }, }, }, + ZeroAuthPaymentIntent: { + Request: { + amount: 0, + setup_future_usage: "off_session", + currency: "USD", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + ZeroAuthConfirmPayment: { + Request: { + payment_type: "setup_mandate", + payment_method: "card", + payment_method_type: "credit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + }, + Response: { + status: 200, + body: { + status: "succeeded", + setup_future_usage: "off_session", + }, + }, + }, SaveCardUseNo3DSAutoCapture: { Request: { payment_method: "card", @@ -391,6 +552,7 @@ export const connectorDetails = { SaveCardUseNo3DSAutoCaptureOffSession: { Request: { payment_method: "card", + payment_method_type: "debit", payment_method_data: { card: successfulNo3DSCardDetails, }, @@ -627,7 +789,7 @@ export const connectorDetails = { }, }, bank_redirect_pm: { - PaymentIntent: getCustomExchange({ + PaymentIntent: { Request: { currency: "EUR", }, @@ -637,7 +799,7 @@ export const connectorDetails = { status: "requires_payment_method", }, }, - }), + }, Ideal: { Request: { payment_method: "bank_redirect", @@ -708,7 +870,10 @@ export const connectorDetails = { Response: { status: 200, body: { - status: "requires_customer_action", + status: "processing", + error_code: "905_1", + error_message: + "Could not find an acquirer account for the provided txvariant (giropay), currency (EUR), and action (AUTH).", }, }, }, @@ -879,4 +1044,215 @@ export const connectorDetails = { }, }, }, + pm_list: { + PmListResponse: { + PmListNull: { + payment_methods: [], + }, + pmListDynamicFieldWithoutBilling: { + payment_methods: [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: [ + { + eligible_connectors: ["adyen"], + }, + ], + required_fields: { + "payment_method_data.card.card_number": { + required_field: "payment_method_data.card.card_number", + display_name: "card_number", + field_type: "user_card_number", + value: null, + }, + "payment_method_data.card.card_exp_month": { + required_field: "payment_method_data.card.card_exp_month", + display_name: "card_exp_month", + field_type: "user_card_expiry_month", + value: null, + }, + "payment_method_data.card.card_exp_year": { + required_field: "payment_method_data.card.card_exp_year", + display_name: "card_exp_year", + field_type: "user_card_expiry_year", + value: null, + }, + "payment_method_data.card.card_cvc": { + required_field: "payment_method_data.card.card_cvc", + display_name: "card_cvc", + field_type: "user_card_cvc", + value: null, + }, + "billing.address.first_name": { + required_field: + "payment_method_data.billing.address.first_name", + display_name: "card_holder_name", + field_type: "user_full_name", + value: null, + }, + "billing.address.last_name": { + required_field: + "payment_method_data.billing.address.last_name", + display_name: "card_holder_name", + field_type: "user_full_name", + value: null, + }, + }, + }, + ], + }, + ], + }, + pmListDynamicFieldWithBilling: { + payment_methods: [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: [ + { + eligible_connectors: ["adyen"], + }, + ], + required_fields: { + "payment_method_data.card.card_exp_month": { + required_field: "payment_method_data.card.card_exp_month", + display_name: "card_exp_month", + field_type: "user_card_expiry_month", + value: null, + }, + "payment_method_data.card.card_number": { + required_field: "payment_method_data.card.card_number", + display_name: "card_number", + field_type: "user_card_number", + value: null, + }, + "payment_method_data.card.card_cvc": { + required_field: "payment_method_data.card.card_cvc", + display_name: "card_cvc", + field_type: "user_card_cvc", + value: null, + }, + "payment_method_data.card.card_exp_year": { + required_field: "payment_method_data.card.card_exp_year", + display_name: "card_exp_year", + field_type: "user_card_expiry_year", + value: null, + }, + "billing.address.first_name": { + required_field: + "payment_method_data.billing.address.first_name", + display_name: "card_holder_name", + field_type: "user_full_name", + value: "joseph", + }, + "billing.address.last_name": { + required_field: + "payment_method_data.billing.address.last_name", + display_name: "card_holder_name", + field_type: "user_full_name", + value: "Doe", + }, + }, + }, + ], + }, + ], + }, + pmListDynamicFieldWithNames: { + payment_methods: [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: [ + { + eligible_connectors: ["adyen"], + }, + ], + required_fields: { + "billing.address.last_name": { + required_field: + "payment_method_data.billing.address.last_name", + display_name: "card_holder_name", + field_type: "user_full_name", + value: "Doe", + }, + "billing.address.first_name": { + required_field: + "payment_method_data.billing.address.first_name", + display_name: "card_holder_name", + field_type: "user_full_name", + value: "joseph", + }, + }, + }, + ], + }, + ], + }, + pmListDynamicFieldWithEmail: { + payment_methods: [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: [ + { + eligible_connectors: ["adyen"], + }, + ], + required_fields: { + "payment_method_data.card.card_exp_month": { + required_field: "payment_method_data.card.card_exp_month", + display_name: "card_exp_month", + field_type: "user_card_expiry_month", + value: null, + }, + "payment_method_data.card.card_number": { + required_field: "payment_method_data.card.card_number", + display_name: "card_number", + field_type: "user_card_number", + value: null, + }, + "payment_method_data.card.card_cvc": { + required_field: "payment_method_data.card.card_cvc", + display_name: "card_cvc", + field_type: "user_card_cvc", + value: null, + }, + "payment_method_data.card.card_exp_year": { + required_field: "payment_method_data.card.card_exp_year", + display_name: "card_exp_year", + field_type: "user_card_expiry_year", + value: null, + }, + "billing.address.first_name": { + required_field: + "payment_method_data.billing.address.first_name", + display_name: "card_holder_name", + field_type: "user_full_name", + value: "joseph", + }, + "billing.address.last_name": { + required_field: + "payment_method_data.billing.address.last_name", + display_name: "card_holder_name", + field_type: "user_full_name", + value: "Doe", + }, + }, + }, + ], + }, + ], + }, + }, + }, }; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/BankOfAmerica.js b/cypress-tests/cypress/e2e/PaymentUtils/BankOfAmerica.js index 17acf01e881f..308565e72fa4 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/BankOfAmerica.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/BankOfAmerica.js @@ -1,7 +1,7 @@ const successfulNo3DSCardDetails = { card_number: "4242424242424242", card_exp_month: "01", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "123", }; @@ -9,7 +9,7 @@ const successfulNo3DSCardDetails = { const successfulThreeDSTestCardDetails = { card_number: "4000000000001091", card_exp_month: "01", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "123", }; @@ -66,6 +66,8 @@ export const connectorDetails = { PaymentIntentOffSession: { Request: { currency: "USD", + amount: 6500, + authentication_type: "no_three_ds", customer_acceptance: null, setup_future_usage: "off_session", }, @@ -76,6 +78,40 @@ export const connectorDetails = { }, }, }, + PaymentIntentWithShippingCost: { + Request: { + currency: "USD", + shipping_cost: 50, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + amount: 6500, + shipping_cost: 50, + }, + }, + }, + PaymentConfirmWithShippingCost: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + shipping_cost: 50, + amount_received: 6550, + amount: 6500, + net_amount: 6550, + }, + }, + }, "3DSManualCapture": { Request: { payment_method: "card", @@ -200,6 +236,7 @@ export const connectorDetails = { }, }, }, + PartialRefund: { Request: { payment_method: "card", @@ -216,6 +253,38 @@ export const connectorDetails = { }, }, }, + manualPaymentRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + manualPaymentPartialRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, SyncRefund: { Request: { payment_method: "card", @@ -360,6 +429,24 @@ export const connectorDetails = { }, }, }, + MITAutoCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MITManualCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, ZeroAuthMandate: { Request: { payment_method: "card", @@ -376,6 +463,37 @@ export const connectorDetails = { }, }, }, + ZeroAuthPaymentIntent: { + Request: { + amount: 0, + setup_future_usage: "off_session", + currency: "USD", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + ZeroAuthConfirmPayment: { + Request: { + payment_type: "setup_mandate", + payment_method: "card", + payment_method_type: "credit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + }, + Response: { + status: 200, + body: { + status: "succeeded", + setup_future_usage: "off_session", + }, + }, + }, SaveCardUseNo3DSAutoCapture: { Request: { payment_method: "card", @@ -403,6 +521,7 @@ export const connectorDetails = { SaveCardUseNo3DSAutoCaptureOffSession: { Request: { payment_method: "card", + payment_method_type: "debit", payment_method_data: { card: successfulNo3DSCardDetails, }, @@ -590,4 +709,265 @@ export const connectorDetails = { }, }, }, + pm_list: { + PmListResponse: { + PmListNull: { + payment_methods: [], + }, + pmListDynamicFieldWithoutBilling: { + payment_methods: [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: [ + { + eligible_connectors: ["bankofamerica"], + }, + ], + required_fields: { + "billing.address.first_name": { + required_field: + "payment_method_data.billing.address.first_name", + display_name: "card_holder_name", + field_type: "user_full_name", + value: null, + }, + "billing.address.state": { + required_field: "payment_method_data.billing.address.state", + display_name: "state", + field_type: "user_address_state", + value: null, + }, + "billing.address.country": { + required_field: + "payment_method_data.billing.address.country", + display_name: "country", + field_type: { + user_address_country: { + options: ["ALL"], + }, + }, + value: null, + }, + "payment_method_data.card.card_exp_year": { + required_field: "payment_method_data.card.card_exp_year", + display_name: "card_exp_year", + field_type: "user_card_expiry_year", + value: null, + }, + "billing.address.zip": { + required_field: "payment_method_data.billing.address.zip", + display_name: "zip", + field_type: "user_address_pincode", + value: null, + }, + "billing.address.last_name": { + required_field: + "payment_method_data.billing.address.last_name", + display_name: "card_holder_name", + field_type: "user_full_name", + value: null, + }, + "payment_method_data.card.card_number": { + required_field: "payment_method_data.card.card_number", + display_name: "card_number", + field_type: "user_card_number", + value: null, + }, + "billing.address.line1": { + required_field: "payment_method_data.billing.address.line1", + display_name: "line1", + field_type: "user_address_line1", + value: null, + }, + "payment_method_data.card.card_exp_month": { + required_field: "payment_method_data.card.card_exp_month", + display_name: "card_exp_month", + field_type: "user_card_expiry_month", + value: null, + }, + email: { + required_field: "email", + display_name: "email", + field_type: "user_email_address", + value: "hyperswitch_sdk_demo_id@gmail.com", + }, + "billing.address.city": { + required_field: "payment_method_data.billing.address.city", + display_name: "city", + field_type: "user_address_city", + value: null, + }, + "payment_method_data.card.card_cvc": { + required_field: "payment_method_data.card.card_cvc", + display_name: "card_cvc", + field_type: "user_card_cvc", + value: null, + }, + }, + }, + ], + }, + ], + }, + pmListDynamicFieldWithBilling: { + payment_methods: [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: [ + { + eligible_connectors: ["bankofamerica"], + }, + ], + required_fields: { + "billing.address.first_name": { + required_field: + "payment_method_data.billing.address.first_name", + display_name: "card_holder_name", + field_type: "user_full_name", + value: "joseph", + }, + "billing.address.state": { + required_field: "payment_method_data.billing.address.state", + display_name: "state", + field_type: "user_address_state", + value: "CA", + }, + "billing.address.country": { + required_field: + "payment_method_data.billing.address.country", + display_name: "country", + field_type: { + user_address_country: { + options: ["ALL"], + }, + }, + value: "PL", + }, + "payment_method_data.card.card_exp_year": { + required_field: "payment_method_data.card.card_exp_year", + display_name: "card_exp_year", + field_type: "user_card_expiry_year", + value: null, + }, + "billing.address.zip": { + required_field: "payment_method_data.billing.address.zip", + display_name: "zip", + field_type: "user_address_pincode", + value: "94122", + }, + "billing.address.last_name": { + required_field: + "payment_method_data.billing.address.last_name", + display_name: "card_holder_name", + field_type: "user_full_name", + value: "Doe", + }, + "payment_method_data.card.card_number": { + required_field: "payment_method_data.card.card_number", + display_name: "card_number", + field_type: "user_card_number", + value: null, + }, + "billing.address.line1": { + required_field: "payment_method_data.billing.address.line1", + display_name: "line1", + field_type: "user_address_line1", + value: "1467", + }, + "payment_method_data.card.card_exp_month": { + required_field: "payment_method_data.card.card_exp_month", + display_name: "card_exp_month", + field_type: "user_card_expiry_month", + value: null, + }, + email: { + required_field: "email", + display_name: "email", + field_type: "user_email_address", + value: "hyperswitch.example@gmail.com", + }, + "billing.address.city": { + required_field: "payment_method_data.billing.address.city", + display_name: "city", + field_type: "user_address_city", + value: "San Fransico", + }, + "payment_method_data.card.card_cvc": { + required_field: "payment_method_data.card.card_cvc", + display_name: "card_cvc", + field_type: "user_card_cvc", + value: null, + }, + }, + }, + ], + }, + ], + }, + pmListDynamicFieldWithNames: { + payment_methods: [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: [ + { + eligible_connectors: ["bankofamerica"], + }, + ], + required_fields: { + "billing.address.last_name": { + required_field: + "payment_method_data.billing.address.last_name", + display_name: "card_holder_name", + field_type: "user_full_name", + value: "Doe", + }, + "billing.address.first_name": { + required_field: + "payment_method_data.billing.address.first_name", + display_name: "card_holder_name", + field_type: "user_full_name", + value: "joseph", + }, + }, + }, + ], + }, + ], + }, + pmListDynamicFieldWithEmail: { + payment_methods: [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: [ + { + eligible_connectors: ["bankofamerica"], + }, + ], + required_fields: { + email: { + required_field: "email", + display_name: "email", + field_type: "user_email_address", + value: "hyperswitch_sdk_demo_id1@gmail.com", + }, + }, + }, + ], + }, + ], + }, + }, + }, }; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Bluesnap.js b/cypress-tests/cypress/e2e/PaymentUtils/Bluesnap.js index 74548be9d419..f27b7ef76c1f 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Bluesnap.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Bluesnap.js @@ -29,7 +29,45 @@ export const connectorDetails = { }, }, }, + PaymentIntentWithShippingCost: { + Request: { + currency: "USD", + amount: 6500, + shipping_cost: 50, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + amount: 6500, + shipping_cost: 50, + }, + }, + }, + PaymentConfirmWithShippingCost: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + shipping_cost: 50, + amount_received: 6550, + amount: 6500, + net_amount: 6550, + }, + }, + }, "3DSManualCapture": { + Configs: { + TRIGGER_SKIP: true, + }, Request: { payment_method: "card", payment_method_data: { @@ -41,13 +79,15 @@ export const connectorDetails = { }, Response: { status: 200, - trigger_skip: true, body: { - status: "requires_capture", + status: "requires_customer_action", }, }, }, "3DSAutoCapture": { + Configs: { + TRIGGER_SKIP: true, + }, Request: { payment_method: "card", payment_method_data: { @@ -59,7 +99,6 @@ export const connectorDetails = { }, Response: { status: 200, - trigger_skip: true, body: { status: "requires_customer_action", }, @@ -171,6 +210,38 @@ export const connectorDetails = { }, }, }, + manualPaymentRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + manualPaymentPartialRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, SyncRefund: { Request: { payment_method: "card", @@ -199,6 +270,40 @@ export const connectorDetails = { }, }, }, + ZeroAuthPaymentIntent: { + Request: { + amount: 0, + setup_future_usage: "off_session", + currency: "USD", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + ZeroAuthConfirmPayment: { + Request: { + payment_type: "setup_mandate", + payment_method: "card", + payment_method_type: "credit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + }, + Response: { + status: 501, + body: { + error: { + type: "invalid_request", + message: "Setup Mandate flow for Bluesnap is not implemented", + code: "IR_00", + }, + }, + }, + }, SaveCardUseNo3DSAutoCapture: { Request: { payment_method: "card", diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Checkout.js b/cypress-tests/cypress/e2e/PaymentUtils/Checkout.js new file mode 100644 index 000000000000..9679b70866bc --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentUtils/Checkout.js @@ -0,0 +1,330 @@ +const successfulNo3DSCardDetails = { + card_number: "4242424242424242", + card_exp_month: "01", + card_exp_year: "50", + card_holder_name: "joseph Doe", + card_cvc: "123", +}; + +const successfulThreeDSTestCardDetails = { + card_number: "4242424242424242", + card_exp_month: "01", + card_exp_year: "50", + card_holder_name: "joseph Doe", + card_cvc: "123", +}; + +const customerAcceptance = { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "127.0.0.1", + user_agent: "amet irure esse", + }, +}; + +export const connectorDetails = { + card_pm: { + PaymentIntent: { + Request: { + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + }, + }, + }, + PaymentIntentWithShippingCost: { + Request: { + currency: "USD", + shipping_cost: 50, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + shipping_cost: 50, + amount: 6500, + }, + }, + }, + PaymentConfirmWithShippingCost: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "processing", + shipping_cost: 50, + amount: 6500, + }, + }, + }, + "3DSManualCapture": { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + "3DSAutoCapture": { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + No3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + No3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "processing", + }, + }, + }, + Capture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + amount: 6500, + amount_capturable: 0, + amount_received: 6500, + }, + }, + }, + PartialCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "partially_captured", + amount: 6500, + amount_capturable: 0, + amount_received: 100, + }, + }, + }, + Void: { + Request: {}, + Response: { + status: 200, + body: { + status: "cancelled", + }, + }, + }, + Refund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + PartialRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + manualPaymentRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + manualPaymentPartialRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SyncRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + ZeroAuthMandate: { + Response: { + status: 501, + body: { + error: { + type: "invalid_request", + message: "Setup Mandate flow for Checkout is not implemented", + code: "IR_00", + }, + }, + }, + }, + ZeroAuthPaymentIntent: { + Request: { + amount: 0, + setup_future_usage: "off_session", + currency: "USD", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + ZeroAuthConfirmPayment: { + Request: { + payment_type: "setup_mandate", + payment_method: "card", + payment_method_type: "credit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + }, + Response: { + status: 501, + body: { + error: { + type: "invalid_request", + message: "Setup Mandate flow for Checkout is not implemented", + code: "IR_00", + }, + }, + }, + }, + SaveCardUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + setup_future_usage: "on_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "processing", + }, + }, + }, + SaveCardUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + setup_future_usage: "on_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + }, +}; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Commons.js b/cypress-tests/cypress/e2e/PaymentUtils/Commons.js index 284eff5fd1d5..85718d8f9ba5 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Commons.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Commons.js @@ -8,9 +8,9 @@ const globalState = new State({ connectorAuthFilePath: Cypress.env("CONNECTOR_AUTH_FILE_PATH"), }); -const connectorName = normalise(globalState.get("connectorId")); +const connectorName = normalize(globalState.get("connectorId")); -function normalise(input) { +function normalize(input) { const exceptions = { bankofamerica: "Bank of America", cybersource: "Cybersource", @@ -18,6 +18,7 @@ function normalise(input) { paypal: "Paypal", wellsfargo: "Wellsfargo", fiuu: "Fiuu", + noon: "Noon", // Add more known exceptions here }; @@ -43,7 +44,7 @@ function normalise(input) { const successfulNo3DSCardDetails = { card_number: "4111111111111111", card_exp_month: "08", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "999", }; @@ -51,11 +52,18 @@ const successfulNo3DSCardDetails = { const successfulThreeDSTestCardDetails = { card_number: "4111111111111111", card_exp_month: "10", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "morino", card_cvc: "999", }; +const PaymentMethodCardDetails = { + card_number: "4111111145551142", + card_exp_month: "03", + card_exp_year: "30", + card_holder_name: "Joseph Doe", +}; + const singleUseMandateData = { customer_acceptance: { acceptance_type: "offline", @@ -90,6 +98,49 @@ const multiUseMandateData = { }, }; +export const cardRequiredField = { + "payment_method_data.card.card_number": { + required_field: "payment_method_data.card.card_number", + display_name: "card_number", + field_type: "user_card_number", + value: null, + }, + "payment_method_data.card.card_exp_year": { + required_field: "payment_method_data.card.card_exp_year", + display_name: "card_exp_year", + field_type: "user_card_expiry_year", + value: null, + }, + "payment_method_data.card.card_cvc": { + required_field: "payment_method_data.card.card_cvc", + display_name: "card_cvc", + field_type: "user_card_cvc", + value: null, + }, + "payment_method_data.card.card_exp_month": { + required_field: "payment_method_data.card.card_exp_month", + display_name: "card_exp_month", + field_type: "user_card_expiry_month", + value: null, + }, +}; + +export const fullNameRequiredField = { + "billing.address.last_name": { + required_field: "payment_method_data.billing.address.last_name", + display_name: "card_holder_name", + field_type: "user_full_name", + value: "Doe", + }, + "billing.address.first_name": { + required_field: "payment_method_data.billing.address.first_name", + display_name: "card_holder_name", + field_type: "user_full_name", + value: "joseph", + }, +}; + +export const billingRequiredField = {}; /* `getDefaultExchange` contains the default Request and Response to be considered if none provided. `getCustomExchange` takes in 2 optional fields named as Request and Response. @@ -98,9 +149,7 @@ with `getCustomExchange`, if 501 response is expected, there is no need to pass // Const to get default PaymentExchange object const getDefaultExchange = () => ({ - Request: { - currency: "EUR", - }, + Request: {}, Response: { status: 501, body: { @@ -135,6 +184,7 @@ export const getCustomExchange = (overrides) => { return { ...defaultExchange, + ...(overrides.Configs ? { Configs: overrides.Configs } : {}), Request: { ...defaultExchange.Request, ...(overrides.Request || {}), @@ -588,6 +638,30 @@ export const connectorDetails = { }, }, }), + PaymentIntentWithShippingCost: getCustomExchange({ + Request: { + currency: "USD", + shipping_cost: 50, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + shipping_cost: 50, + amount: 6500, + }, + }, + }), + PaymentConfirmWithShippingCost: getCustomExchange({ + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + }), "3DSManualCapture": getCustomExchange({ Request: { payment_method: "card", @@ -632,6 +706,20 @@ export const connectorDetails = { setup_future_usage: "on_session", }, }), + No3DSFailPayment: getCustomExchange({ + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: {}, + }, + }), Capture: getCustomExchange({ Request: { payment_method: "card", @@ -666,6 +754,27 @@ export const connectorDetails = { }, }, }), + VoidAfterConfirm: getCustomExchange({ + Request: {}, + Response: { + status: 200, + body: { + status: "cancelled", + capture_method: "manual", + }, + }, + ResponseCustom: { + status: 400, + body: { + error: { + type: "invalid_request", + message: + "You cannot cancel this payment because it has status succeeded", + code: "IR_16", + }, + }, + }, + }), Refund: getCustomExchange({ Request: { payment_method: "card", @@ -686,6 +795,38 @@ export const connectorDetails = { }, }, }), + manualPaymentRefund: getCustomExchange({ + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "pending", + }, + }, + }), + manualPaymentPartialRefund: getCustomExchange({ + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "pending", + }, + }, + }), PartialRefund: getCustomExchange({ Request: { payment_method: "card", @@ -796,6 +937,23 @@ export const connectorDetails = { mandate_data: singleUseMandateData, }, }), + ZeroAuthPaymentIntent: getCustomExchange({ + Request: { + amount: 0, + setup_future_usage: "off_session", + currency: "USD", + payment_type: "setup_mandate", + }, + }), + ZeroAuthConfirmPayment: getCustomExchange({ + Request: { + payment_type: "setup_mandate", + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + }, + }), SaveCardUseNo3DSAutoCapture: getCustomExchange({ Request: { payment_method: "card", @@ -852,12 +1010,35 @@ export const connectorDetails = { Request: { setup_future_usage: "off_session", }, + ResponseCustom: { + status: 400, + body: { + error: { + message: + "No eligible connector was found for the current payment method configuration", + type: "invalid_request", + }, + }, + }, }), SaveCardConfirmManualCaptureOffSession: getCustomExchange({ Request: { setup_future_usage: "off_session", }, }), + SaveCardConfirmAutoCaptureOffSessionWithoutBilling: { + Request: { + setup_future_usage: "off_session", + billing: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + billing: null, + }, + }, + }, SaveCardUseNo3DSManualCapture: getCustomExchange({ Request: { payment_method: "card", @@ -876,6 +1057,19 @@ export const connectorDetails = { }, }, }), + PaymentMethod: { + Request: { + payment_method: "card", + payment_method_type: "credit", + payment_method_issuer: "Gpay", + payment_method_issuer_code: "jp_hdfc", + card: PaymentMethodCardDetails, + }, + Response: { + status: 200, + body: {}, + }, + }, PaymentMethodIdMandateNo3DSAutoCapture: getCustomExchange({ Request: { payment_method: "card", @@ -968,7 +1162,11 @@ export const connectorDetails = { Response: { status: 400, body: { - error: "Json deserialize error: invalid card number length", + error: { + error_type: "invalid_request", + message: "Json deserialize error: invalid card number length", + code: "IR_06", + }, }, }, }, @@ -1072,8 +1270,12 @@ export const connectorDetails = { Response: { status: 400, body: { - error: - "Json deserialize error: unknown variant `United`, expected one of `AED`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BRL`, `BSD`, `BWP`, `BYN`, `BZD`, `CAD`, `CHF`, `CLP`, `CNY`, `COP`, `CRC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRU`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SEK`, `SGD`, `SHP`, `SLE`, `SLL`, `SOS`, `SRD`, `SSP`, `STN`, `SVC`, `SZL`, `THB`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYU`, `UZS`, `VES`, `VND`, `VUV`, `WST`, `XAF`, `XCD`, `XOF`, `XPF`, `YER`, `ZAR`, `ZMW`", + error: { + error_type: "invalid_request", + message: + "Json deserialize error: unknown variant `United`, expected one of `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BRL`, `BSD`, `BTN`, `BWP`, `BYN`, `BZD`, `CAD`, `CDF`, `CHF`, `CLP`, `CNY`, `COP`, `CRC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRU`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLE`, `SLL`, `SOS`, `SRD`, `SSP`, `STN`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYU`, `UZS`, `VES`, `VND`, `VUV`, `WST`, `XAF`, `XCD`, `XOF`, `XPF`, `YER`, `ZAR`, `ZMW`, `ZWL`", + code: "IR_06", + }, }, }, }, @@ -1097,8 +1299,12 @@ export const connectorDetails = { Response: { status: 400, body: { - error: - "Json deserialize error: unknown variant `auto`, expected one of `automatic`, `manual`, `manual_multiple`, `scheduled`", + error: { + error_type: "invalid_request", + message: + "Json deserialize error: unknown variant `auto`, expected one of `automatic`, `manual`, `manual_multiple`, `scheduled`", + code: "IR_06", + }, }, }, }, @@ -1121,8 +1327,12 @@ export const connectorDetails = { Response: { status: 400, body: { - error: - "Json deserialize error: unknown variant `this_supposed_to_be_a_card`, expected one of `card`, `card_redirect`, `pay_later`, `wallet`, `bank_redirect`, `bank_transfer`, `crypto`, `bank_debit`, `reward`, `real_time_payment`, `upi`, `voucher`, `gift_card`, `open_banking`", + error: { + error_type: "invalid_request", + message: + "Json deserialize error: unknown variant `this_supposed_to_be_a_card`, expected one of `card`, `card_redirect`, `pay_later`, `wallet`, `bank_redirect`, `bank_transfer`, `crypto`, `bank_debit`, `reward`, `real_time_payment`, `upi`, `voucher`, `gift_card`, `open_banking`, `mobile_payment`", + code: "IR_06", + }, }, }, }, @@ -1190,7 +1400,8 @@ export const connectorDetails = { body: { error: { type: "invalid_request", - message: "A payment token or payment method data is required", + message: + "A payment token or payment method data or ctp service details is required", code: "IR_06", }, }, @@ -1262,6 +1473,115 @@ export const connectorDetails = { }, }, }), + MITAutoCapture: getCustomExchange({ + Request: {}, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + ResponseCustom: { + status: 400, + body: { + error: { + message: + "No eligible connector was found for the current payment method configuration", + type: "invalid_request", + }, + }, + }, + }), + PaymentWithoutBilling: { + Request: { + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + authentication_type: "no_three_ds", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + }, + }, + }, + PaymentWithBilling: { + Request: { + currency: "USD", + setup_future_usage: "on_session", + billing: { + address: { + line1: "1467", + line2: "CA", + line3: "Harrison Street", + city: "San Fransico", + state: "CA", + zip: "94122", + country: "PL", + first_name: "joseph", + last_name: "Doe", + }, + phone: { + number: "9111222333", + country_code: "+91", + }, + }, + email: "hyperswitch.example@gmail.com", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + }, + }, + }, + PaymentWithFullName: { + Request: { + currency: "USD", + setup_future_usage: "on_session", + billing: { + address: { + first_name: "joseph", + last_name: "Doe", + }, + phone: { + number: "9111222333", + country_code: "+91", + }, + }, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + }, + }, + }, + PaymentWithBillingEmail: { + Request: { + currency: "USD", + setup_future_usage: "on_session", + email: "hyperswitch_sdk_demo_id1@gmail.com", + billing: { + address: { + first_name: "joseph", + last_name: "Doe", + }, + phone: { + number: "9111222333", + country_code: "+91", + }, + email: "hyperswitch.example@gmail.com", + }, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + }, + }, + }, }, upi_pm: { PaymentIntent: getCustomExchange({ @@ -1300,4 +1620,67 @@ export const connectorDetails = { }, }), }, + pm_list: { + PmListResponse: { + PmListNull: { + payment_methods: [], + }, + pmListDynamicFieldWithoutBilling: { + payment_methods: [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: [], + required_fields: {}, + }, + ], + }, + ], + }, + pmListDynamicFieldWithBilling: { + payment_methods: [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: [], + required_fields: {}, + }, + ], + }, + ], + }, + pmListDynamicFieldWithNames: { + payment_methods: [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: [], + required_fields: {}, + }, + ], + }, + ], + }, + pmListDynamicFieldWithEmail: { + payment_methods: [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: [], + required_fields: {}, + }, + ], + }, + ], + }, + }, + }, }; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Cybersource.js b/cypress-tests/cypress/e2e/PaymentUtils/Cybersource.js index d71f11b4e6e4..b98973250e98 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Cybersource.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Cybersource.js @@ -1,7 +1,12 @@ +import { + connectorDetails as commonConnectorDetails, + getCustomExchange, +} from "./Commons"; + const successfulNo3DSCardDetails = { card_number: "4242424242424242", card_exp_month: "01", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "123", }; @@ -9,7 +14,7 @@ const successfulNo3DSCardDetails = { const successfulThreeDSTestCardDetails = { card_number: "4000000000001091", card_exp_month: "01", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "123", }; @@ -48,6 +53,66 @@ const multiUseMandateData = { }, }; +const payment_method_data_no3ds = { + card: { + last4: "4242", + card_type: "CREDIT", + card_network: "Visa", + card_issuer: "STRIPE PAYMENTS UK LIMITED", + card_issuing_country: "UNITEDKINGDOM", + card_isin: "424242", + card_extended_bin: null, + card_exp_month: "01", + card_exp_year: "50", + card_holder_name: "joseph Doe", + payment_checks: { + avs_response: { + code: "Y", + codeRaw: "Y", + }, + card_verification: null, + }, + authentication_data: null, + }, + billing: null, +}; + +const payment_method_data_3ds = { + card: { + last4: "1091", + card_type: "CREDIT", + card_network: "Visa", + card_issuer: "INTL HDQTRS-CENTER OWNED", + card_issuing_country: "UNITEDSTATES", + card_isin: "400000", + card_extended_bin: null, + card_exp_month: "01", + card_exp_year: "50", + card_holder_name: "joseph Doe", + payment_checks: null, + authentication_data: null, + }, + billing: null, +}; + +const billing_with_newline = { + address: { + line1: "1467", + line2: "Harrison Street\nApt 101", + line3: "Harrison Street\nApt 101", + city: "San Fransico\n city", + state: "California", + zip: "94122", + country: "NL", + first_name: "joseph", + last_name: "Doe", + }, + phone: { + number: "9123456789", + country_code: "+91", + }, +}; + export const connectorDetails = { card_pm: { PaymentIntent: { @@ -60,12 +125,21 @@ export const connectorDetails = { status: 200, body: { status: "requires_payment_method", + setup_future_usage: "on_session", }, }, }, PaymentIntentOffSession: { + Configs: { + CONNECTOR_CREDENTIAL: { + specName: ["incrementalAuth"], + value: "connector_2", + }, + }, Request: { currency: "USD", + amount: 6500, + authentication_type: "no_three_ds", customer_acceptance: null, setup_future_usage: "off_session", }, @@ -73,10 +147,50 @@ export const connectorDetails = { status: 200, body: { status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + PaymentIntentWithShippingCost: { + Request: { + currency: "USD", + shipping_cost: 50, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + shipping_cost: 50, + amount: 6500, + }, + }, + }, + PaymentConfirmWithShippingCost: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + shipping_cost: 50, + amount_received: 6550, + amount: 6500, + net_amount: 6550, }, }, }, "3DSManualCapture": { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -89,11 +203,18 @@ export const connectorDetails = { Response: { status: 200, body: { - status: "requires_capture", + status: "requires_customer_action", + setup_future_usage: "on_session", + payment_method_data: payment_method_data_3ds, }, }, }, "3DSAutoCapture": { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -107,10 +228,17 @@ export const connectorDetails = { status: 200, body: { status: "requires_customer_action", + setup_future_usage: "on_session", + payment_method_data: payment_method_data_3ds, }, }, }, No3DSManualCapture: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -124,10 +252,18 @@ export const connectorDetails = { status: 200, body: { status: "requires_capture", + payment_method: "card", + attempt_count: 1, + payment_method_data: payment_method_data_no3ds, }, }, }, No3DSAutoCapture: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -136,15 +272,24 @@ export const connectorDetails = { currency: "USD", customer_acceptance: null, setup_future_usage: "on_session", + billing: billing_with_newline, }, Response: { status: 200, body: { status: "succeeded", + payment_method: "card", + attempt_count: 1, + payment_method_data: payment_method_data_no3ds, }, }, }, Capture: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -164,6 +309,11 @@ export const connectorDetails = { }, }, PartialCapture: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: {}, Response: { status: 200, @@ -176,6 +326,11 @@ export const connectorDetails = { }, }, Void: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: {}, Response: { status: 200, @@ -185,6 +340,11 @@ export const connectorDetails = { }, }, Refund: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -200,7 +360,64 @@ export const connectorDetails = { }, }, }, + manualPaymentRefund: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, + Request: { + payment_method: "card", + payment_method_data: { + 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", + }, + }, + }, + }, + manualPaymentPartialRefund: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, + Request: { + payment_method: "card", + payment_method_data: { + 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", + }, + }, + }, + }, PartialRefund: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -217,6 +434,11 @@ export const connectorDetails = { }, }, SyncRefund: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -232,7 +454,26 @@ export const connectorDetails = { }, }, }, + IncrementalAuth: { + Request: { + amount: 7000, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + amount: 7000, + amount_capturable: 7000, + amount_received: null, + }, + }, + }, MandateSingleUse3DSAutoCapture: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -249,6 +490,11 @@ export const connectorDetails = { }, }, MandateSingleUse3DSManualCapture: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -265,6 +511,11 @@ export const connectorDetails = { }, }, MandateSingleUseNo3DSAutoCapture: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -281,6 +532,11 @@ export const connectorDetails = { }, }, MandateSingleUseNo3DSManualCapture: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -297,6 +553,11 @@ export const connectorDetails = { }, }, MandateMultiUseNo3DSAutoCapture: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -313,6 +574,11 @@ export const connectorDetails = { }, }, MandateMultiUseNo3DSManualCapture: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -329,6 +595,11 @@ export const connectorDetails = { }, }, MandateMultiUse3DSAutoCapture: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -345,6 +616,11 @@ export const connectorDetails = { }, }, MandateMultiUse3DSManualCapture: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -360,7 +636,34 @@ export const connectorDetails = { }, }, }, + MITAutoCapture: getCustomExchange({ + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, + ...commonConnectorDetails.card_pm.MITAutoCapture, + }), + MITManualCapture: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, + Request: {}, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, ZeroAuthMandate: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -376,9 +679,51 @@ export const connectorDetails = { }, }, }, + ZeroAuthPaymentIntent: { + Request: { + amount: 0, + setup_future_usage: "off_session", + currency: "USD", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + ZeroAuthConfirmPayment: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, + Request: { + payment_type: "setup_mandate", + payment_method: "card", + payment_method_type: "credit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + }, + Response: { + status: 200, + body: { + status: "succeeded", + setup_future_usage: "off_session", + }, + }, + }, SaveCardUseNo3DSAutoCapture: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: { payment_method: "card", + payment_method_type: "debit", payment_method_data: { card: successfulNo3DSCardDetails, }, @@ -401,8 +746,14 @@ export const connectorDetails = { }, }, SaveCardUseNo3DSAutoCaptureOffSession: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: { payment_method: "card", + payment_method_type: "debit", payment_method_data: { card: successfulNo3DSCardDetails, }, @@ -424,8 +775,15 @@ export const connectorDetails = { }, }, SaveCardUseNo3DSManualCaptureOffSession: { + Configs: { + CONNECTOR_CREDENTIAL: { + specName: ["incrementalAuth"], + value: "connector_2", + }, + }, Request: { payment_method: "card", + payment_method_type: "debit", payment_method_data: { card: successfulNo3DSCardDetails, }, @@ -447,6 +805,11 @@ export const connectorDetails = { }, }, SaveCardConfirmAutoCaptureOffSession: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: { setup_future_usage: "off_session", }, @@ -458,6 +821,11 @@ export const connectorDetails = { }, }, SaveCardConfirmManualCaptureOffSession: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: { setup_future_usage: "off_session", }, @@ -469,6 +837,11 @@ export const connectorDetails = { }, }, SaveCardUseNo3DSManualCapture: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -493,6 +866,11 @@ export const connectorDetails = { }, }, PaymentMethodIdMandateNo3DSAutoCapture: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -517,6 +895,11 @@ export const connectorDetails = { }, }, PaymentMethodIdMandateNo3DSManualCapture: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -541,6 +924,11 @@ export const connectorDetails = { }, }, PaymentMethodIdMandate3DSAutoCapture: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -566,6 +954,11 @@ export const connectorDetails = { }, }, PaymentMethodIdMandate3DSManualCapture: { + Configs: { + CONNECTOR_CREDENTIAL: { + value: "connector_1", + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -590,4 +983,255 @@ export const connectorDetails = { }, }, }, + pm_list: { + PmListResponse: { + PmListNull: { + payment_methods: [], + }, + pmListDynamicFieldWithoutBilling: { + payment_methods: [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: [ + { + eligible_connectors: ["cybersource"], + }, + ], + required_fields: { + "billing.address.first_name": { + required_field: + "payment_method_data.billing.address.first_name", + display_name: "card_holder_name", + field_type: "user_full_name", + value: null, + }, + "payment_method_data.card.card_number": { + required_field: "payment_method_data.card.card_number", + display_name: "card_number", + field_type: "user_card_number", + value: null, + }, + "payment_method_data.card.card_cvc": { + required_field: "payment_method_data.card.card_cvc", + display_name: "card_cvc", + field_type: "user_card_cvc", + value: null, + }, + + "payment_method_data.card.card_exp_year": { + required_field: "payment_method_data.card.card_exp_year", + display_name: "card_exp_year", + field_type: "user_card_expiry_year", + value: null, + }, + "billing.address.last_name": { + required_field: + "payment_method_data.billing.address.last_name", + display_name: "card_holder_name", + field_type: "user_full_name", + value: null, + }, + "billing.address.state": { + required_field: "payment_method_data.billing.address.state", + display_name: "state", + field_type: "user_address_state", + value: null, + }, + "billing.email": { + required_field: "payment_method_data.billing.email", + display_name: "email", + field_type: "user_email_address", + value: "hyperswitch_sdk_demo_id@gmail.com", + }, + "billing.address.zip": { + required_field: "payment_method_data.billing.address.zip", + display_name: "zip", + field_type: "user_address_pincode", + value: null, + }, + "payment_method_data.card.card_exp_month": { + required_field: "payment_method_data.card.card_exp_month", + display_name: "card_exp_month", + field_type: "user_card_expiry_month", + value: null, + }, + "billing.address.line1": { + required_field: "payment_method_data.billing.address.line1", + display_name: "line1", + field_type: "user_address_line1", + value: null, + }, + "billing.address.city": { + required_field: "payment_method_data.billing.address.city", + display_name: "city", + field_type: "user_address_city", + value: null, + }, + }, + }, + ], + }, + ], + }, + pmListDynamicFieldWithBilling: { + payment_methods: [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: [ + { + eligible_connectors: ["cybersource"], + }, + ], + required_fields: { + "billing.address.city": { + required_field: "payment_method_data.billing.address.city", + display_name: "city", + field_type: "user_address_city", + value: "San Fransico", + }, + "billing.address.state": { + required_field: "payment_method_data.billing.address.state", + display_name: "state", + field_type: "user_address_state", + value: "CA", + }, + "billing.address.zip": { + required_field: "payment_method_data.billing.address.zip", + display_name: "zip", + field_type: "user_address_pincode", + value: "94122", + }, + "billing.address.country": { + required_field: + "payment_method_data.billing.address.country", + display_name: "country", + field_type: { + user_address_country: { + options: ["ALL"], + }, + }, + value: "PL", + }, + "billing.address.first_name": { + required_field: + "payment_method_data.billing.address.first_name", + display_name: "card_holder_name", + field_type: "user_full_name", + value: "joseph", + }, + "billing.address.last_name": { + required_field: + "payment_method_data.billing.address.last_name", + display_name: "card_holder_name", + field_type: "user_full_name", + value: "Doe", + }, + "billing.email": { + required_field: "payment_method_data.billing.email", + display_name: "email", + field_type: "user_email_address", + value: "hyperswitch.example@gmail.com", + }, + "payment_method_data.card.card_cvc": { + required_field: "payment_method_data.card.card_cvc", + display_name: "card_cvc", + field_type: "user_card_cvc", + value: null, + }, + "billing.address.line1": { + required_field: "payment_method_data.billing.address.line1", + display_name: "line1", + field_type: "user_address_line1", + value: "1467", + }, + "payment_method_data.card.card_exp_month": { + required_field: "payment_method_data.card.card_exp_month", + display_name: "card_exp_month", + field_type: "user_card_expiry_month", + value: null, + }, + "payment_method_data.card.card_number": { + required_field: "payment_method_data.card.card_number", + display_name: "card_number", + field_type: "user_card_number", + value: null, + }, + "payment_method_data.card.card_exp_year": { + required_field: "payment_method_data.card.card_exp_year", + display_name: "card_exp_year", + field_type: "user_card_expiry_year", + value: null, + }, + }, + }, + ], + }, + ], + }, + pmListDynamicFieldWithNames: { + payment_methods: [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: [ + { + eligible_connectors: ["cybersource"], + }, + ], + required_fields: { + "billing.address.last_name": { + required_field: + "payment_method_data.billing.address.last_name", + display_name: "card_holder_name", + field_type: "user_full_name", + value: "Doe", + }, + "billing.address.first_name": { + required_field: + "payment_method_data.billing.address.first_name", + display_name: "card_holder_name", + field_type: "user_full_name", + value: "joseph", + }, + }, + }, + ], + }, + ], + }, + pmListDynamicFieldWithEmail: { + payment_methods: [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: [ + { + eligible_connectors: ["cybersource"], + }, + ], + required_fields: { + "billing.email": { + required_field: "payment_method_data.billing.email", + display_name: "email", + field_type: "user_email_address", + value: "hyperswitch.example@gmail.com", + }, + }, + }, + ], + }, + ], + }, + }, + }, }; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Datatrans.js b/cypress-tests/cypress/e2e/PaymentUtils/Datatrans.js index a2285f2c101d..13bbf7e7d348 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Datatrans.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Datatrans.js @@ -1,7 +1,7 @@ const successfulNo3DSCardDetails = { card_number: "4444090101010103", card_exp_month: "06", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "123", }; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Elavon.js b/cypress-tests/cypress/e2e/PaymentUtils/Elavon.js new file mode 100644 index 000000000000..6755dd16e700 --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentUtils/Elavon.js @@ -0,0 +1,572 @@ +const successfulNo3DSCardDetails = { + card_number: "4111111111111111", + card_exp_month: "06", + card_exp_year: "50", + card_holder_name: "joseph Doe", + card_cvc: "123", +}; + +const singleUseMandateData = { + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + mandate_type: { + single_use: { + amount: 8000, + currency: "USD", + }, + }, +}; + +const multiUseMandateData = { + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + mandate_type: { + multi_use: { + amount: 8000, + currency: "USD", + }, + }, +}; +export const connectorDetails = { + card_pm: { + PaymentIntent: { + Request: { + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + billing: { + address: { + line1: "1467", + line2: "CA", + line3: "CA", + city: "Florence", + state: "Tuscany", + zip: "12345", + country: "IT", + first_name: "Max", + last_name: "Mustermann", + }, + email: "mauro.morandi@nexi.it", + phone: { + number: "9123456789", + country_code: "+91", + }, + }, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + }, + }, + }, + No3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: { + email: "mauro.morandi@nexi.it", + }, + }, + billing: { + email: "mauro.morandi@nexi.it", + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + No3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: { + email: "mauro.morandi@nexi.it", + }, + }, + billing: { + email: "mauro.morandi@nexi.it", + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + manualPaymentPartialRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + manualPaymentRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MandateMultiUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: { + address: { + line1: "1467", + line2: "Harrison Street", + line3: "Harrison Street", + city: "San Fransico", + state: "California", + zip: "94122", + country: "NL", + first_name: "joseph", + last_name: "Doe", + }, + email: "johndoe@gmail.com", + }, + }, + currency: "USD", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MandateMultiUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: { + address: { + line1: "1467", + line2: "Harrison Street", + line3: "Harrison Street", + city: "San Fransico", + state: "California", + zip: "94122", + country: "NL", + first_name: "joseph", + last_name: "Doe", + }, + email: "johndoe@gmail.com", + }, + }, + currency: "USD", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + SaveCardUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: { + email: "mauro.morandi@nexi.it", + }, + }, + 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", + }, + }, + }, + SaveCardUseNo3DSAutoCaptureOffSession: { + Request: { + payment_method: "card", + payment_method_type: "debit", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: { + address: { + line1: "1467", + line2: "Harrison Street", + line3: "Harrison Street", + city: "San Fransico", + state: "California", + zip: "94122", + country: "NL", + first_name: "joseph", + last_name: "Doe", + }, + phone: { + number: "9123456789", + country_code: "+91", + }, + email: "mauro.morandi@nexi.it", + }, + }, + setup_future_usage: "off_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", + }, + }, + }, + SaveCardUseNo3DSManualCaptureOffSession: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: { + address: { + line1: "1467", + line2: "Harrison Street", + line3: "Harrison Street", + city: "San Fransico", + state: "California", + zip: "94122", + country: "NL", + first_name: "joseph", + last_name: "Doe", + }, + phone: { + number: "9123456789", + country_code: "+91", + }, + email: "mauro.morandi@nexi.it", + }, + }, + setup_future_usage: "off_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", + }, + }, + }, + SaveCardConfirmAutoCaptureOffSession: { + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardConfirmManualCaptureOffSession: { + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + SaveCardUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: { + address: { + line1: "1467", + line2: "Harrison Street", + line3: "Harrison Street", + city: "San Fransico", + state: "California", + zip: "94122", + country: "NL", + first_name: "joseph", + last_name: "Doe", + }, + phone: { + number: "9123456789", + country_code: "+91", + }, + email: "mauro.morandi@nexi.it", + }, + }, + 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", + }, + }, + }, + MandateSingleUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: { + email: "mauro.morandi@nexi.it", + }, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MandateSingleUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: { + email: "mauro.morandi@nexi.it", + }, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + Capture: { + Request: { + payment_method: "card", + payment_method_data: { + 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, + }, + }, + }, + Refund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + VoidAfterConfirm: { + Request: {}, + Response: { + status: 501, + body: { + error: { + type: "invalid_request", + message: "Cancel/Void flow is not implemented", + code: "IR_00", + }, + }, + }, + }, + PartialRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SyncRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + PaymentMethodIdMandateNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + billing: { + email: "mauro.morandi@nexi.it", + }, + mandate_data: null, + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + PaymentMethodIdMandateNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + billing: { + email: "mauro.morandi@nexi.it", + }, + currency: "USD", + mandate_data: null, + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + }, +}; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Fiservemea.js b/cypress-tests/cypress/e2e/PaymentUtils/Fiservemea.js index 9c5c9e40975e..1460b474220f 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Fiservemea.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Fiservemea.js @@ -1,7 +1,7 @@ const successfulNo3DSCardDetails = { card_number: "5204740000001002", card_exp_month: "10", - card_exp_year: "24", + card_exp_year: "50", card_holder_name: "Joseph Doe", card_cvc: "002", }; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Fiuu.js b/cypress-tests/cypress/e2e/PaymentUtils/Fiuu.js index 86e2314c9279..996356117ab0 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Fiuu.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Fiuu.js @@ -9,9 +9,43 @@ const successfulNo3DSCardDetails = { const successfulThreeDSTestCardDetails = { card_number: "5105105105105100", card_exp_month: "12", - card_exp_year: "2030", + card_exp_year: "2031", card_holder_name: "joseph Doe", - card_cvc: "123", + card_cvc: "444", +}; + +const singleUseMandateData = { + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + mandate_type: { + single_use: { + amount: 8000, + currency: "USD", + }, + }, +}; + +const multiUseMandateData = { + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + mandate_type: { + multi_use: { + amount: 8000, + currency: "USD", + }, + }, }; export const connectorDetails = { card_pm: { @@ -41,7 +75,7 @@ export const connectorDetails = { Response: { status: 200, body: { - status: "requires_capture", + status: "requires_customer_action", }, }, }, @@ -184,5 +218,494 @@ export const connectorDetails = { }, }, }, + MandateSingleUse3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MandateSingleUse3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + MandateSingleUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MandateSingleUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + MandateMultiUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: { + address: { + line1: "1467", + line2: "Harrison Street", + line3: "Harrison Street", + city: "San Fransico", + state: "California", + zip: "94122", + country: "NL", + first_name: "joseph", + last_name: "Doe", + }, + email: "johndoe@gmail.com", + }, + }, + currency: "USD", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MandateMultiUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + MandateMultiUse3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "USD", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + MandateMultiUse3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "USD", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + MITAutoCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "failed", + error_code: "The currency not allow for the RecordType", + error_message: "The currency not allow for the RecordType", + }, + }, + }, + MITManualCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "failed", + error_code: "The currency not allow for the RecordType", + error_message: "The currency not allow for the RecordType", + }, + }, + }, + PaymentMethodIdMandateNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: { + address: { + line1: "1467", + line2: "Harrison Street", + line3: "Harrison Street", + city: "San Fransico", + state: "California", + zip: "94122", + country: "NL", + first_name: "joseph", + last_name: "Doe", + }, + email: "johndoe@gmail.com", + }, + }, + currency: "USD", + mandate_data: null, + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: { + address: { + line1: "1467", + line2: "Harrison Street", + line3: "Harrison Street", + city: "San Fransico", + state: "California", + zip: "94122", + country: "NL", + first_name: "joseph", + last_name: "Doe", + }, + email: "johndoe@gmail.com", + }, + }, + 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: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: { + address: { + line1: "1467", + line2: "Harrison Street", + line3: "Harrison Street", + city: "San Fransico", + state: "California", + zip: "94122", + country: "NL", + first_name: "joseph", + last_name: "Doe", + }, + email: "johndoe@gmail.com", + }, + }, + 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", + }, + }, + }, + SaveCardUseNo3DSAutoCaptureOffSession: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: { + address: { + line1: "1467", + line2: "Harrison Street", + line3: "Harrison Street", + city: "San Fransico", + state: "California", + zip: "94122", + country: "NL", + first_name: "joseph", + last_name: "Doe", + }, + email: "johndoe@gmail.com", + }, + }, + setup_future_usage: "off_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", + }, + }, + }, + SaveCardUseNo3DSManualCaptureOffSession: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: { + address: { + line1: "1467", + line2: "Harrison Street", + line3: "Harrison Street", + city: "San Fransico", + state: "California", + zip: "94122", + country: "NL", + first_name: "joseph", + last_name: "Doe", + }, + email: "johndoe@gmail.com", + }, + }, + setup_future_usage: "off_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", + }, + }, + }, + SaveCardConfirmAutoCaptureOffSession: { + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardConfirmManualCaptureOffSession: { + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + PaymentMethodIdMandateNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + billing: { + address: { + line1: "1467", + line2: "Harrison Street", + line3: "Harrison Street", + city: "San Fransico", + state: "California", + zip: "94122", + country: "NL", + first_name: "joseph", + last_name: "Doe", + }, + email: "johndoe@gmail.com", + }, + }, + currency: "USD", + mandate_data: null, + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + PaymentMethodIdMandate3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + billing: { + address: { + line1: "1467", + line2: "Harrison Street", + line3: "Harrison Street", + city: "San Fransico", + state: "California", + zip: "94122", + country: "NL", + first_name: "joseph", + last_name: "Doe", + }, + email: "johndoe@gmail.com", + }, + }, + currency: "USD", + mandate_data: null, + authentication_type: "three_ds", + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + PaymentMethodIdMandate3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + billing: { + address: { + line1: "1467", + line2: "Harrison Street", + line3: "Harrison Street", + city: "San Fransico", + state: "California", + zip: "94122", + country: "NL", + first_name: "joseph", + last_name: "Doe", + }, + email: "johndoe@gmail.com", + }, + }, + mandate_data: null, + authentication_type: "three_ds", + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, }, }; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Iatapay.js b/cypress-tests/cypress/e2e/PaymentUtils/Iatapay.js index 3eddf0530155..faa810e7bad5 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Iatapay.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Iatapay.js @@ -1,3 +1,11 @@ +const successfulNo3DSCardDetails = { + card_number: "4111111111111111", + card_exp_month: "03", + card_exp_year: "30", + card_holder_name: "John Doe", + card_cvc: "737", +}; + export const connectorDetails = { bank_redirect_pm: { Ideal: { @@ -46,6 +54,40 @@ export const connectorDetails = { }, }, }, + ZeroAuthPaymentIntent: { + Request: { + amount: 0, + setup_future_usage: "off_session", + currency: "USD", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + ZeroAuthConfirmPayment: { + Request: { + payment_type: "setup_mandate", + payment_method: "card", + payment_method_type: "credit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + }, + Response: { + status: 501, + body: { + error: { + type: "invalid_request", + message: "Setup Mandate flow for Iatapay is not implemented", + code: "IR_00", + }, + }, + }, + }, }, upi_pm: { PaymentIntent: { diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Jpmorgan.js b/cypress-tests/cypress/e2e/PaymentUtils/Jpmorgan.js new file mode 100644 index 000000000000..36e53ffd995f --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentUtils/Jpmorgan.js @@ -0,0 +1,225 @@ +const successfulNo3DSCardDetails = { + card_number: "6011016011016011", + card_exp_month: "10", + card_exp_year: "2027", + card_holder_name: "John Doe", + card_cvc: "123", +}; + +export const connectorDetails = { + card_pm: { + PaymentIntent: { + Request: { + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + }, + }, + }, + "3DSManualCapture": { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 501, + body: { + error: { + type: "invalid_request", + message: "3DS payments is not supported by Jpmorgan", + code: "IR_00", + }, + }, + }, + }, + + "3DSAutoCapture": { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 501, + body: { + error: { + type: "invalid_request", + message: "Three_ds payments is not supported by Jpmorgan", + code: "IR_00", + }, + }, + }, + }, + No3DSManualCapture: { + Request: { + currency: "USD", + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + No3DSAutoCapture: { + Request: { + currency: "USD", + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + Capture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + 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, + }, + }, + }, + Refund: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 501, + body: { + type: "invalid_request", + message: "Refunds is not implemented", + code: "IR_00", + }, + }, + }, + manualPaymentRefund: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 501, + body: { + type: "invalid_request", + message: "Refunds is not implemented", + code: "IR_00", + }, + }, + }, + manualPaymentPartialRefund: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 501, + body: { + type: "invalid_request", + message: "Refunds is not implemented", + code: "IR_00", + }, + }, + }, + PartialRefund: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 501, + body: { + type: "invalid_request", + message: "Refunds is not implemented", + code: "IR_00", + }, + }, + }, + SyncRefund: { + Configs: { + TRIGGER_SKIP: true, + }, + Response: { + status: 404, + body: { + type: "invalid_request", + message: "Refund does not exist in our records.", + code: "HE_02", + }, + }, + }, + }, +}; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Nexixpay.js b/cypress-tests/cypress/e2e/PaymentUtils/Nexixpay.js new file mode 100644 index 000000000000..3b77a0ad6e7f --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentUtils/Nexixpay.js @@ -0,0 +1,599 @@ +const successfulNo3DSCardDetails = { + card_number: "4111111111111111", + card_exp_month: "08", + card_exp_year: "35", + card_holder_name: "joseph Doe", + card_cvc: "999", +}; + +const successfulThreeDSTestCardDetails = { + card_number: "4349940199004549", + card_exp_month: "12", + card_exp_year: "35", + card_holder_name: "joseph Doe", + card_cvc: "396", +}; + +const customerAcceptance = { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, +}; + +const multiUseMandateData = { + customer_acceptance: customerAcceptance, + mandate_type: { + multi_use: { + amount: 8000, + currency: "EUR", + }, + }, +}; + +const singleUseMandateData = { + customer_acceptance: customerAcceptance, + mandate_type: { + multi_use: { + amount: 8000, + currency: "EUR", + }, + }, +}; + +const billingAddress = { + address: { + line1: "1467", + line2: "Harrison Street", + line3: "Harrison Street", + city: "San Fransico", + state: "California", + zip: "94122", + country: "IT", + first_name: "joseph", + last_name: "Doe", + }, + email: "mauro.morandi@nexi.it", + phone: { + number: "9123456789", + country_code: "+91", + }, +}; + +const no3DSNotSupportedResponseBody = { + error: { + type: "invalid_request", + message: "No threeds is not supported", + code: "IR_00", + }, +}; + +export const connectorDetails = { + card_pm: { + PaymentIntent: { + Request: { + currency: "EUR", + amount: 6500, + customer_acceptance: null, + setup_future_usage: "on_session", + billing: billingAddress, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + }, + }, + }, + PaymentIntentOffSession: { + Request: { + currency: "EUR", + amount: 6500, + authentication_type: "no_three_ds", + customer_acceptance: null, + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + "3DSManualCapture": { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + billing: billingAddress, + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + "3DSAutoCapture": { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + billing: billingAddress, + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + No3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + customer_acceptance: null, + setup_future_usage: "on_session", + billing: billingAddress, + }, + Response: { + status: 400, + body: no3DSNotSupportedResponseBody, + }, + }, + No3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + customer_acceptance: null, + setup_future_usage: "on_session", + billing: billingAddress, + }, + Response: { + status: 400, + body: no3DSNotSupportedResponseBody, + }, + }, + Capture: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "processing", + amount: 6500, + amount_capturable: 6500, + amount_received: null, + }, + }, + }, + PartialCapture: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: {}, + Response: { + status: 200, + body: { + status: "processing", + amount: 6500, + amount_capturable: 6500, + amount_received: 100, + }, + }, + }, + Void: { + Request: {}, + Response: { + status: 200, + body: { + status: "cancelled", + }, + }, + }, + Refund: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "pending", + }, + }, + }, + PartialRefund: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "pending", + }, + }, + }, + SyncRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MandateMultiUse3DSAutoCapture: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + mandate_data: multiUseMandateData, + billing: billingAddress, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + MandateMultiUse3DSManualCapture: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + mandate_data: multiUseMandateData, + billing: billingAddress, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + MandateMultiUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + mandate_data: multiUseMandateData, + billing: billingAddress, + }, + Response: { + status: 400, + body: no3DSNotSupportedResponseBody, + }, + }, + MandateMultiUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + mandate_data: multiUseMandateData, + billing: billingAddress, + }, + Response: { + status: 400, + body: no3DSNotSupportedResponseBody, + }, + }, + MandateSingleUse3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + mandate_data: singleUseMandateData, + billing: billingAddress, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + MandateSingleUse3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + mandate_data: singleUseMandateData, + billing: billingAddress, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + MandateSingleUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + mandate_data: singleUseMandateData, + billing: billingAddress, + }, + Response: { + status: 400, + body: no3DSNotSupportedResponseBody, + }, + }, + MandateSingleUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + mandate_data: singleUseMandateData, + billing: billingAddress, + }, + Response: { + status: 400, + body: no3DSNotSupportedResponseBody, + }, + }, + manualPaymentRefund: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "pending", + }, + }, + }, + ZeroAuthMandate: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "processing", + }, + }, + }, + PaymentMethodIdMandateNo3DSAutoCapture: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + amount: 6500, + mandate_data: null, + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + PaymentMethodIdMandateNo3DSManualCapture: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + amount: 6500, + mandate_data: null, + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + PaymentMethodIdMandate3DSAutoCapture: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + amount: 6500, + mandate_data: null, + authentication_type: "three_ds", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + PaymentMethodIdMandate3DSManualCapture: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + amount: 6500, + mandate_data: null, + authentication_type: "three_ds", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + SaveCardUseNo3DSAutoCapture: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_type: "debit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + billing: billingAddress, + setup_future_usage: "on_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + SaveCardUseNo3DSAutoCaptureOffSession: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + currency: "EUR", + billing: billingAddress, + payment_method_type: "debit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + setup_future_usage: "off_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + SaveCardUseNo3DSManualCaptureOffSession: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + currency: "EUR", + billing: billingAddress, + payment_method_type: "debit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + setup_future_usage: "off_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + }, +}; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Nmi.js b/cypress-tests/cypress/e2e/PaymentUtils/Nmi.js index 30a9e3eb9b23..8ad218953ddd 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Nmi.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Nmi.js @@ -1,7 +1,7 @@ const successfulNo3DSCardDetails = { card_number: "4000000000002503", card_exp_month: "08", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "999", }; @@ -9,11 +9,20 @@ const successfulNo3DSCardDetails = { const successfulThreeDSTestCardDetails = { card_number: "4000000000002503", card_exp_month: "10", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "morino", card_cvc: "999", }; +const customerAcceptance = { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "127.0.0.1", + user_agent: "amet irure esse", + }, +}; + export const connectorDetails = { card_pm: { PaymentIntent: { @@ -29,13 +38,44 @@ export const connectorDetails = { }, }, }, + PaymentIntentWithShippingCost: { + Request: { + currency: "USD", + shipping_cost: 50, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + shipping_cost: 50, + amount: 6500, + }, + }, + }, + PaymentConfirmWithShippingCost: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "processing", + shipping_cost: 50, + amount: 6500, + }, + }, + }, "3DSManualCapture": { Request: { payment_method: "card", payment_method_data: { card: successfulThreeDSTestCardDetails, }, - currency: "USD", customer_acceptance: null, setup_future_usage: "on_session", }, @@ -52,7 +92,6 @@ export const connectorDetails = { payment_method_data: { card: successfulThreeDSTestCardDetails, }, - currency: "USD", customer_acceptance: null, setup_future_usage: "on_session", }, @@ -69,7 +108,6 @@ export const connectorDetails = { payment_method_data: { card: successfulNo3DSCardDetails, }, - currency: "USD", customer_acceptance: null, setup_future_usage: "on_session", }, @@ -86,7 +124,6 @@ export const connectorDetails = { payment_method_data: { card: successfulNo3DSCardDetails, }, - currency: "USD", customer_acceptance: null, setup_future_usage: "on_session", }, @@ -103,7 +140,6 @@ export const connectorDetails = { payment_method_data: { card: successfulNo3DSCardDetails, }, - currency: "USD", customer_acceptance: null, }, Response: { @@ -127,6 +163,15 @@ export const connectorDetails = { }, }, Void: { + Request: {}, + Response: { + status: 200, + body: { + status: "cancelled", + }, + }, + }, + VoidAfterConfirm: { Request: {}, Response: { status: 400, @@ -140,13 +185,13 @@ export const connectorDetails = { }, }, }, + Refund: { Request: { payment_method: "card", payment_method_data: { card: successfulNo3DSCardDetails, }, - currency: "USD", customer_acceptance: null, }, Response: { @@ -162,7 +207,36 @@ export const connectorDetails = { payment_method_data: { card: successfulNo3DSCardDetails, }, - currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "pending", + }, + }, + }, + manualPaymentRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "pending", + }, + }, + }, + manualPaymentPartialRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, customer_acceptance: null, }, Response: { @@ -178,7 +252,6 @@ export const connectorDetails = { payment_method_data: { card: successfulNo3DSCardDetails, }, - currency: "USD", customer_acceptance: null, }, Response: { @@ -200,23 +273,48 @@ export const connectorDetails = { }, }, }, - SaveCardUseNo3DSAutoCapture: { + ZeroAuthPaymentIntent: { + Request: { + amount: 0, + setup_future_usage: "off_session", + currency: "USD", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + ZeroAuthConfirmPayment: { Request: { + payment_type: "setup_mandate", payment_method: "card", payment_method_data: { 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: 501, + body: { + error: { + type: "invalid_request", + message: "Setup Mandate flow for Nmi is not implemented", + code: "IR_00", }, }, }, + }, + SaveCardUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + setup_future_usage: "on_session", + customer_acceptance: customerAcceptance, + }, Response: { status: 200, body: { @@ -230,16 +328,8 @@ export const connectorDetails = { payment_method_data: { 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", - }, - }, + customer_acceptance: customerAcceptance, }, Response: { status: 200, @@ -248,5 +338,45 @@ export const connectorDetails = { }, }, }, + PaymentMethodIdMandate3DSAutoCapture: { + Configs: { + // Skipping redirection here for mandate 3ds auto capture as it requires changes from the core + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + mandate_data: null, + authentication_type: "three_ds", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + PaymentMethodIdMandate3DSManualCapture: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + mandate_data: null, + authentication_type: "three_ds", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, }, }; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Noon.js b/cypress-tests/cypress/e2e/PaymentUtils/Noon.js new file mode 100644 index 000000000000..63f6b6317324 --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentUtils/Noon.js @@ -0,0 +1,719 @@ +const successfulNo3DSCardDetails = { + card_number: "4242424242424242", + card_exp_month: "01", + card_exp_year: "30", + card_holder_name: "joseph Doe", + card_cvc: "123", +}; + +const successfulThreeDSTestCardDetails = { + card_number: "4000000000001091", + card_exp_month: "01", + card_exp_year: "30", + card_holder_name: "joseph Doe", + card_cvc: "123", +}; + +const customerAcceptance = { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, +}; + +const connectorMetadata = { + noon: { + order_category: "pay", + }, +}; + +const singleUseMandateData = { + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + mandate_type: { + single_use: { + amount: 8000, + currency: "USD", + }, + }, +}; + +const multiUseMandateData = { + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + mandate_type: { + multi_use: { + amount: 8000, + currency: "USD", + }, + }, +}; + +const payment_method_data_3ds = { + card: { + last4: "1091", + card_type: "CREDIT", + card_network: "Visa", + card_issuer: "INTL HDQTRS-CENTER OWNED", + card_issuing_country: "UNITEDSTATES", + card_isin: "400000", + card_extended_bin: null, + card_exp_month: "01", + card_exp_year: "30", + card_holder_name: null, + payment_checks: null, + authentication_data: null, + }, + billing: null, +}; + +export const connectorDetails = { + card_pm: { + PaymentIntent: { + Request: { + currency: "AED", + customer_acceptance: null, + setup_future_usage: "on_session", + connector_metadata: connectorMetadata, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "on_session", + }, + }, + }, + PaymentIntentOffSession: { + Request: { + currency: "AED", + amount: 6500, + authentication_type: "no_three_ds", + customer_acceptance: null, + setup_future_usage: "off_session", + connector_metadata: connectorMetadata, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + PaymentIntentWithShippingCost: { + Request: { + currency: "AED", + shipping_cost: 50, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + shipping_cost: 50, + amount: 6500, + }, + }, + }, + PaymentConfirmWithShippingCost: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + shipping_cost: 50, + amount: 6500, + }, + }, + }, + "3DSManualCapture": { + Request: { + payment_method: "card", + currency: "AED", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + connector_metadata: connectorMetadata, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + setup_future_usage: "on_session", + payment_method_data: payment_method_data_3ds, + }, + }, + }, + "3DSAutoCapture": { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "AED", + customer_acceptance: null, + connector_metadata: connectorMetadata, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + setup_future_usage: "on_session", + payment_method_data: payment_method_data_3ds, + }, + }, + }, + No3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + connector_metadata: connectorMetadata, + customer_acceptance: null, + currency: "AED", + setup_future_usage: "on_session", + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "requires_customer_action", + }, + }, + }, + No3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + connector_metadata: connectorMetadata, + currency: "AED", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "requires_customer_action", + }, + }, + }, + Capture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + 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, + }, + }, + }, + Void: { + Request: {}, + Response: { + status: 200, + body: { + status: "cancelled", + }, + }, + }, + Refund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "AED", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + manualPaymentRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "AED", + customer_acceptance: null, + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "failed", + }, + }, + }, + manualPaymentPartialRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "failed", + }, + }, + }, + PartialRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SyncRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MandateSingleUse3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + connector_metadata: connectorMetadata, + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "requires_customer_action", + }, + }, + }, + MandateSingleUse3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + MandateSingleUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "AED", + mandate_data: singleUseMandateData, + connector_metadata: connectorMetadata, + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "requires_customer_action", + }, + }, + }, + MandateSingleUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "AED", + mandate_data: singleUseMandateData, + connector_metadata: connectorMetadata, + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "requires_customer_action", + }, + }, + }, + MandateMultiUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "AED", + mandate_data: multiUseMandateData, + connector_metadata: connectorMetadata, + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "requires_customer_action", + }, + }, + }, + MandateMultiUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "AED", + mandate_data: multiUseMandateData, + connector_metadata: connectorMetadata, + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "requires_customer_action", + }, + }, + }, + MandateMultiUse3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + MandateMultiUse3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + MITAutoCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MITManualCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + ZeroAuthMandate: { + Response: { + status: 501, + body: { + error: { + type: "invalid_request", + message: "Setup Mandate flow for Noon is not implemented", + code: "IR_00", + }, + }, + }, + }, + ZeroAuthPaymentIntent: { + Request: { + amount: 0, + setup_future_usage: "off_session", + currency: "AED", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + ZeroAuthConfirmPayment: { + Request: { + payment_type: "setup_mandate", + payment_method: "card", + payment_method_type: "credit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + }, + Response: { + status: 501, + body: { + error: { + type: "invalid_request", + message: "Setup Mandate flow for Noon is not implemented", + code: "IR_00", + }, + }, + }, + }, + SaveCardUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_type: "debit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "AED", + setup_future_usage: "on_session", + customer_acceptance: customerAcceptance, + connector_metadata: connectorMetadata, + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "requires_customer_action", + }, + }, + }, + SaveCardUseNo3DSAutoCaptureOffSession: { + Request: { + payment_method: "card", + payment_method_type: "debit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + setup_future_usage: "off_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "requires_customer_action", + }, + }, + }, + SaveCardUseNo3DSManualCaptureOffSession: { + Request: { + payment_method: "card", + payment_method_type: "debit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + setup_future_usage: "off_session", + customer_acceptance: customerAcceptance, + connector_metadata: connectorMetadata, + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "requires_customer_action", + }, + }, + }, + SaveCardConfirmAutoCaptureOffSession: { + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "requires_customer_action", + }, + }, + }, + SaveCardConfirmManualCaptureOffSession: { + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + SaveCardUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + setup_future_usage: "on_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "requires_customer_action", + }, + }, + }, + PaymentMethodIdMandateNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "AED", + mandate_data: null, + customer_acceptance: customerAcceptance, + connector_metadata: connectorMetadata, + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "requires_customer_action", + }, + }, + }, + PaymentMethodIdMandateNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "AED", + mandate_data: null, + customer_acceptance: customerAcceptance, + connector_metadata: connectorMetadata, + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "requires_customer_action", + }, + }, + }, + PaymentMethodIdMandate3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "AED", + mandate_data: null, + authentication_type: "three_ds", + customer_acceptance: customerAcceptance, + connector_metadata: connectorMetadata, + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "requires_customer_action", + }, + }, + }, + PaymentMethodIdMandate3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "AED", + mandate_data: null, + authentication_type: "three_ds", + customer_acceptance: customerAcceptance, + connector_metadata: connectorMetadata, + }, + Response: { + status: 200, + trigger_skip: true, + body: { + status: "requires_customer_action", + }, + }, + }, + }, +}; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Novalnet.js b/cypress-tests/cypress/e2e/PaymentUtils/Novalnet.js index e0db22b347d2..0a8235141dcc 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Novalnet.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Novalnet.js @@ -1,15 +1,7 @@ -const successfulNo3DSCardDetails = { - card_number: "4200000000000000", - card_exp_month: "12", - card_exp_year: "25", - card_holder_name: "Max Mustermann", - card_cvc: "123", -}; - const successfulThreeDSTestCardDetails = { card_number: "4000000000001091", card_exp_month: "12", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "Max Mustermann", card_cvc: "123", }; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Paybox.js b/cypress-tests/cypress/e2e/PaymentUtils/Paybox.js index c54349b325e8..2b85b7e5cd99 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Paybox.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Paybox.js @@ -6,6 +6,51 @@ const successfulNo3DSCardDetails = { card_cvc: "222", }; +const successfulThreeDSTestCardDetails = { + card_number: "4000000000001091", + card_exp_month: "01", + card_exp_year: "50", + card_holder_name: "joseph Doe", + card_cvc: "123", +}; + +const customerAcceptance = { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, +}; + +const singleUseMandateData = { + customer_acceptance: customerAcceptance, + mandate_type: { + single_use: { + amount: 7000, + currency: "EUR", + }, + }, +}; + +const multiUseMandateData = { + customer_acceptance: customerAcceptance, + mandate_type: { + multi_use: { + amount: 6500, + currency: "EUR", + }, + }, +}; + +const captureNotSupported = { + type: "invalid_request", + message: "Payment method type not supported", + code: "IR_19", + reason: + "Capture Not allowed in case of Creating the Subscriber is not supported by Paybox", +}; + export const connectorDetails = { card_pm: { PaymentIntent: { @@ -21,6 +66,22 @@ export const connectorDetails = { }, }, }, + PaymentIntentOffSession: { + Request: { + currency: "EUR", + amount: 6500, + authentication_type: "no_three_ds", + customer_acceptance: null, + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, No3DSManualCapture: { Request: { currency: "EUR", @@ -52,7 +113,43 @@ export const connectorDetails = { Response: { status: 200, body: { - status: "processing", + status: "succeeded", + }, + }, + }, + "3DSManualCapture": { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + setup_future_usage: "on_session", + }, + }, + }, + "3DSAutoCapture": { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + setup_future_usage: "on_session", }, }, }, @@ -67,10 +164,10 @@ export const connectorDetails = { Response: { status: 200, body: { - status: "processing", + status: "succeeded", amount: 6500, - amount_capturable: 6500, - amount_received: null, + amount_capturable: 0, + amount_received: 6500, }, }, }, @@ -79,10 +176,24 @@ export const connectorDetails = { Response: { status: 200, body: { - status: "processing", + status: "partially_captured", amount: 6500, - amount_capturable: 6500, - amount_received: null, + amount_capturable: 0, + amount_received: 100, + }, + }, + }, + VoidAfterConfirm: { + Request: {}, + Response: { + status: 501, + body: { + status: "cancelled", + error: { + type: "invalid_request", + message: "Cancel/Void flow is not implemented", + code: "IR_00", + }, }, }, }, @@ -131,7 +242,363 @@ export const connectorDetails = { }, }, }, - + MandateSingleUse3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + mandate_data: singleUseMandateData, + }, + Response: { + status: 400, + body: { + error: captureNotSupported, + }, + }, + }, + MandateSingleUse3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + MandateSingleUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + mandate_data: singleUseMandateData, + }, + Response: { + status: 400, + body: { + error: captureNotSupported, + }, + }, + }, + MandateSingleUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + MandateMultiUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + error: captureNotSupported, + }, + }, + }, + MandateMultiUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + MandateMultiUse3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + mandate_data: multiUseMandateData, + }, + Response: { + status: 400, + body: { + error: captureNotSupported, + }, + }, + }, + MandateMultiUse3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + MITAutoCapture: { + Request: { + currency: "EUR", + amount: 6500, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MITManualCapture: { + Request: { + currency: "EUR", + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + PaymentMethodIdMandateNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + mandate_data: null, + customer_acceptance: customerAcceptance, + }, + Response: { + status: 400, + body: { + error: captureNotSupported, + }, + }, + }, + PaymentMethodIdMandateNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + mandate_data: null, + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + PaymentMethodIdMandate3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + mandate_data: null, + authentication_type: "three_ds", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 400, + body: { + error: captureNotSupported, + }, + }, + }, + PaymentMethodIdMandate3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + mandate_data: null, + authentication_type: "three_ds", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + ZeroAuthMandate: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "processing", + }, + }, + }, + ZeroAuthPaymentIntent: { + Request: { + amount: 0, + setup_future_usage: "off_session", + currency: "EUR", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + ZeroAuthConfirmPayment: { + Request: { + payment_type: "setup_mandate", + payment_method: "card", + payment_method_type: "credit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + }, + Response: { + status: 200, + body: { + status: "processing", + }, + }, + }, + SaveCardUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_type: "debit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + setup_future_usage: "on_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardUseNo3DSAutoCaptureOffSession: { + Request: { + payment_method: "card", + payment_method_type: "debit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + setup_future_usage: "off_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 400, + body: { + error: captureNotSupported, + }, + }, + }, + SaveCardUseNo3DSManualCaptureOffSession: { + Request: { + payment_method: "card", + payment_method_type: "debit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + setup_future_usage: "off_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + SaveCardConfirmAutoCaptureOffSession: { + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardConfirmManualCaptureOffSession: { + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + SaveCardUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + setup_future_usage: "on_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, InvalidCardNumber: { Request: { currency: "EUR", @@ -142,7 +609,7 @@ export const connectorDetails = { card: { card_number: "123456", card_exp_month: "10", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "123", }, @@ -151,7 +618,11 @@ export const connectorDetails = { Response: { status: 400, body: { - error: "Json deserialize error: invalid card number length", + error: { + error_type: "invalid_request", + message: "Json deserialize error: invalid card number length", + code: "IR_06", + }, }, }, }, @@ -255,8 +726,12 @@ export const connectorDetails = { Response: { status: 400, body: { - error: - "Json deserialize error: unknown variant `United`, expected one of `AED`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BRL`, `BSD`, `BWP`, `BYN`, `BZD`, `CAD`, `CHF`, `CLP`, `CNY`, `COP`, `CRC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRU`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SEK`, `SGD`, `SHP`, `SLE`, `SLL`, `SOS`, `SRD`, `SSP`, `STN`, `SVC`, `SZL`, `THB`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYU`, `UZS`, `VES`, `VND`, `VUV`, `WST`, `XAF`, `XCD`, `XOF`, `XPF`, `YER`, `ZAR`, `ZMW`", + error: { + error_type: "invalid_request", + message: + "Json deserialize error: unknown variant `United`, expected one of `AED`, `AFN`, `ALL`, `AMD`, `ANG`, `AOA`, `ARS`, `AUD`, `AWG`, `AZN`, `BAM`, `BBD`, `BDT`, `BGN`, `BHD`, `BIF`, `BMD`, `BND`, `BOB`, `BRL`, `BSD`, `BTN`, `BWP`, `BYN`, `BZD`, `CAD`, `CDF`, `CHF`, `CLP`, `CNY`, `COP`, `CRC`, `CUP`, `CVE`, `CZK`, `DJF`, `DKK`, `DOP`, `DZD`, `EGP`, `ERN`, `ETB`, `EUR`, `FJD`, `FKP`, `GBP`, `GEL`, `GHS`, `GIP`, `GMD`, `GNF`, `GTQ`, `GYD`, `HKD`, `HNL`, `HRK`, `HTG`, `HUF`, `IDR`, `ILS`, `INR`, `IQD`, `IRR`, `ISK`, `JMD`, `JOD`, `JPY`, `KES`, `KGS`, `KHR`, `KMF`, `KPW`, `KRW`, `KWD`, `KYD`, `KZT`, `LAK`, `LBP`, `LKR`, `LRD`, `LSL`, `LYD`, `MAD`, `MDL`, `MGA`, `MKD`, `MMK`, `MNT`, `MOP`, `MRU`, `MUR`, `MVR`, `MWK`, `MXN`, `MYR`, `MZN`, `NAD`, `NGN`, `NIO`, `NOK`, `NPR`, `NZD`, `OMR`, `PAB`, `PEN`, `PGK`, `PHP`, `PKR`, `PLN`, `PYG`, `QAR`, `RON`, `RSD`, `RUB`, `RWF`, `SAR`, `SBD`, `SCR`, `SDG`, `SEK`, `SGD`, `SHP`, `SLE`, `SLL`, `SOS`, `SRD`, `SSP`, `STN`, `SVC`, `SYP`, `SZL`, `THB`, `TJS`, `TMT`, `TND`, `TOP`, `TRY`, `TTD`, `TWD`, `TZS`, `UAH`, `UGX`, `USD`, `UYU`, `UZS`, `VES`, `VND`, `VUV`, `WST`, `XAF`, `XCD`, `XOF`, `XPF`, `YER`, `ZAR`, `ZMW`, `ZWL`", + code: "IR_06", + }, }, }, }, @@ -280,8 +755,12 @@ export const connectorDetails = { Response: { status: 400, body: { - error: - "Json deserialize error: unknown variant `auto`, expected one of `automatic`, `manual`, `manual_multiple`, `scheduled`", + error: { + error_type: "invalid_request", + message: + "Json deserialize error: unknown variant `auto`, expected one of `automatic`, `manual`, `manual_multiple`, `scheduled`", + code: "IR_06", + }, }, }, }, @@ -304,8 +783,12 @@ export const connectorDetails = { Response: { status: 400, body: { - error: - "Json deserialize error: unknown variant `this_supposed_to_be_a_card`, expected one of `card`, `card_redirect`, `pay_later`, `wallet`, `bank_redirect`, `bank_transfer`, `crypto`, `bank_debit`, `reward`, `real_time_payment`, `upi`, `voucher`, `gift_card`, `open_banking`", + error: { + error_type: "invalid_request", + message: + "Json deserialize error: unknown variant `this_supposed_to_be_a_card`, expected one of `card`, `card_redirect`, `pay_later`, `wallet`, `bank_redirect`, `bank_transfer`, `crypto`, `bank_debit`, `reward`, `real_time_payment`, `upi`, `voucher`, `gift_card`, `open_banking`, `mobile_payment`", + code: "IR_06", + }, }, }, }, @@ -373,7 +856,8 @@ export const connectorDetails = { body: { error: { type: "invalid_request", - message: "A payment token or payment method data is required", + message: + "A payment token or payment method data or ctp service details is required", code: "IR_06", }, }, @@ -401,29 +885,6 @@ export const connectorDetails = { }, }, }, - CaptureCapturedAmount: { - Request: { - Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "EUR", - 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 succeeded. The expected state is requires_capture, partially_captured_and_capturable, processing", - code: "IR_14", - }, - }, - }, - }, ConfirmSuccessfulPayment: { Request: { payment_method: "card", diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Paypal.js b/cypress-tests/cypress/e2e/PaymentUtils/Paypal.js index dd21b84ee2a8..b30e989a2b73 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Paypal.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Paypal.js @@ -3,7 +3,7 @@ import { getCustomExchange } from "./Commons"; const successfulNo3DSCardDetails = { card_number: "4012000033330026", card_exp_month: "01", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "123", }; @@ -11,11 +11,40 @@ const successfulNo3DSCardDetails = { const successfulThreeDSTestCardDetails = { card_number: "4868719460707704", card_exp_month: "01", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "123", }; +const customerAcceptance = { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, +}; + +const singleUseMandateData = { + customer_acceptance: customerAcceptance, + mandate_type: { + single_use: { + amount: 8000, + currency: "USD", + }, + }, +}; + +const multiUseMandateData = { + customer_acceptance: customerAcceptance, + mandate_type: { + multi_use: { + amount: 8000, + currency: "USD", + }, + }, +}; + export const connectorDetails = { card_pm: { PaymentIntent: { @@ -31,7 +60,43 @@ export const connectorDetails = { }, }, }, + PaymentIntentWithShippingCost: { + Request: { + currency: "USD", + shipping_cost: 50, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + shipping_cost: 50, + amount: 6500, + }, + }, + }, + PaymentConfirmWithShippingCost: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "failed", + error_code: "AMOUNT_MISMATCH", + error_message: + "description - Should equal item_total + tax_total + shipping + handling + insurance - shipping_discount - discount., value - 65.50, field - value;", + }, + }, + }, "3DSManualCapture": { + Configs: { + TRIGGER_SKIP: true, + }, Request: { payment_method: "card", payment_method_data: { @@ -43,13 +108,15 @@ export const connectorDetails = { }, Response: { status: 200, - trigger_skip: true, body: { - status: "requires_capture", + status: "requires_customer_action", }, }, }, "3DSAutoCapture": { + Configs: { + TRIGGER_SKIP: true, + }, Request: { payment_method: "card", payment_method_data: { @@ -61,7 +128,6 @@ export const connectorDetails = { }, Response: { status: 200, - trigger_skip: true, body: { status: "requires_customer_action", }, @@ -173,6 +239,38 @@ export const connectorDetails = { }, }, }, + manualPaymentRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + manualPaymentPartialRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, SyncRefund: { Request: { payment_method: "card", @@ -190,33 +288,87 @@ export const connectorDetails = { }, }, ZeroAuthMandate: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, Response: { - status: 501, + status: 200, body: { - error: { - type: "invalid_request", - message: "Setup Mandate flow for Paypal is not implemented", - code: "IR_00", - }, + status: "succeeded", }, }, }, - SaveCardUseNo3DSAutoCapture: { + ZeroAuthPaymentIntent: { Request: { + amount: 0, + setup_future_usage: "off_session", + currency: "USD", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + ZeroAuthConfirmPayment: { + Request: { + payment_type: "setup_mandate", payment_method: "card", + payment_method_type: "credit", payment_method_data: { card: successfulNo3DSCardDetails, }, + }, + Response: { + status: 200, + body: { + status: "succeeded", + setup_future_usage: "off_session", + }, + }, + }, + SaveCardConfirmAutoCaptureOffSession: { + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + PaymentIntentOffSession: { + Request: { 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", - }, + amount: 6500, + authentication_type: "no_three_ds", + customer_acceptance: null, + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + SaveCardUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, }, + setup_future_usage: "on_session", + customer_acceptance: customerAcceptance, }, Response: { status: 200, @@ -231,16 +383,41 @@ export const connectorDetails = { payment_method_data: { 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", - }, + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + SaveCardUseNo3DSAutoCaptureOffSession: { + Request: { + payment_method: "card", + payment_method_type: "debit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + setup_future_usage: "off_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardUseNo3DSManualCaptureOffSession: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, }, + setup_future_usage: "off_session", + customer_acceptance: customerAcceptance, }, Response: { status: 200, @@ -249,6 +426,227 @@ export const connectorDetails = { }, }, }, + SaveCardConfirmManualCaptureOffSession: { + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + MandateSingleUse3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MandateSingleUse3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + MandateSingleUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MandateSingleUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + MandateMultiUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MandateMultiUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + MandateMultiUse3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + MandateMultiUse3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + MITAutoCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MITManualCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + PaymentMethodIdMandateNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + mandate_data: null, + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + PaymentMethodIdMandateNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + mandate_data: null, + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + PaymentMethodIdMandate3DSAutoCapture: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + mandate_data: null, + authentication_type: "three_ds", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + PaymentMethodIdMandate3DSManualCapture: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + mandate_data: null, + authentication_type: "three_ds", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, }, bank_redirect_pm: { PaymentIntent: getCustomExchange({ diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Stripe.js b/cypress-tests/cypress/e2e/PaymentUtils/Stripe.js index d34ae2a82542..b8dd275afae3 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Stripe.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Stripe.js @@ -1,9 +1,13 @@ -import { getCustomExchange } from "./Commons"; +import { + cardRequiredField, + connectorDetails as commonConnectorDetails, + getCustomExchange, +} from "./Commons"; const successfulNo3DSCardDetails = { card_number: "4242424242424242", card_exp_month: "10", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "morino", card_cvc: "737", }; @@ -11,11 +15,19 @@ const successfulNo3DSCardDetails = { const successfulThreeDSTestCardDetails = { card_number: "4000002500003155", card_exp_month: "10", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "morino", card_cvc: "737", }; +const failedNo3DSCardDetails = { + card_number: "4000000000000002", + card_exp_month: "01", + card_exp_year: "25", + card_holder_name: "joseph Doe", + card_cvc: "123", +}; + const singleUseMandateData = { customer_acceptance: { acceptance_type: "offline", @@ -50,7 +62,70 @@ const multiUseMandateData = { }, }; +const payment_method_data_3ds = { + card: { + last4: "3155", + card_type: "CREDIT", + card_network: "Visa", + card_issuer: "INTL HDQTRS-CENTER OWNED", + card_issuing_country: "UNITEDSTATES", + card_isin: "400000", + card_extended_bin: null, + card_exp_month: "10", + card_exp_year: "50", + card_holder_name: "morino", + payment_checks: null, + authentication_data: null, + }, + billing: null, +}; + +const payment_method_data_no3ds = { + card: { + last4: "4242", + card_type: "CREDIT", + card_network: "Visa", + card_issuer: "STRIPE PAYMENTS UK LIMITED", + card_issuing_country: "UNITEDKINGDOM", + card_isin: "424242", + card_extended_bin: null, + card_exp_month: "10", + card_exp_year: "50", + card_holder_name: "morino", + payment_checks: { + cvc_check: "pass", + address_line1_check: "pass", + address_postal_code_check: "pass", + }, + authentication_data: null, + }, + billing: null, +}; + +const requiredFields = { + payment_methods: [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: [ + { + eligible_connectors: ["stripe"], + }, + ], + required_fields: cardRequiredField, + }, + ], + }, + ], +}; + export const connectorDetails = { + multi_credential_config: { + specName: ["connectorAgnostic"], + value: "connector_2", + }, card_pm: { PaymentIntent: { Request: { @@ -62,19 +137,63 @@ export const connectorDetails = { status: 200, body: { status: "requires_payment_method", + setup_future_usage: "on_session", }, }, }, PaymentIntentOffSession: { + Configs: { + CONNECTOR_CREDENTIAL: { + specName: ["connectorAgnostic"], + value: "connector_2", + }, + }, Request: { currency: "USD", customer_acceptance: null, + amount: 6500, + authentication_type: "no_three_ds", setup_future_usage: "off_session", }, Response: { status: 200, body: { status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + PaymentIntentWithShippingCost: { + Request: { + currency: "USD", + shipping_cost: 50, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + shipping_cost: 50, + amount: 6500, + }, + }, + }, + PaymentConfirmWithShippingCost: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + shipping_cost: 50, + amount_received: 6550, + amount: 6500, + net_amount: 6550, }, }, }, @@ -91,10 +210,13 @@ export const connectorDetails = { Response: { status: 200, body: { - status: "requires_capture", + status: "requires_customer_action", + setup_future_usage: "on_session", + payment_method_data: payment_method_data_3ds, }, }, }, + "3DSAutoCapture": { Request: { payment_method: "card", @@ -109,6 +231,8 @@ export const connectorDetails = { status: 200, body: { status: "requires_customer_action", + setup_future_usage: "on_session", + payment_method_data: payment_method_data_3ds, }, }, }, @@ -126,6 +250,9 @@ export const connectorDetails = { status: 200, body: { status: "requires_capture", + payment_method: "card", + attempt_count: 1, + payment_method_data: payment_method_data_no3ds, }, }, }, @@ -143,6 +270,30 @@ export const connectorDetails = { status: 200, body: { status: "succeeded", + payment_method: "card", + attempt_count: 1, + payment_method_data: payment_method_data_no3ds, + }, + }, + }, + No3DSFailPayment: { + Request: { + payment_method: "card", + payment_method_data: { + card: failedNo3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "failed", + error_code: "card_declined", + error_message: + "message - Your card was declined., decline_code - generic_decline", + unified_code: "UE_9000", + unified_message: "Something went wrong", }, }, }, @@ -202,6 +353,37 @@ export const connectorDetails = { }, }, }, + manualPaymentRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + manualPaymentPartialRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, PartialRefund: { Request: { payment_method: "card", @@ -362,6 +544,24 @@ export const connectorDetails = { }, }, }, + MITAutoCapture: getCustomExchange({ + Configs: { + CONNECTOR_CREDENTIAL: { + specName: ["connectorAgnostic"], + value: "connector_2", + }, + }, + ...commonConnectorDetails.card_pm.MITAutoCapture, + }), + MITManualCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, ZeroAuthMandate: { Request: { payment_method: "card", @@ -378,6 +578,37 @@ export const connectorDetails = { }, }, }, + ZeroAuthPaymentIntent: { + Request: { + amount: 0, + setup_future_usage: "off_session", + currency: "USD", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + ZeroAuthConfirmPayment: { + Request: { + payment_type: "setup_mandate", + payment_method: "card", + payment_method_type: "credit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + }, + Response: { + status: 200, + body: { + status: "succeeded", + setup_future_usage: "off_session", + }, + }, + }, SaveCardUseNo3DSAutoCapture: { Request: { payment_method: "card", @@ -451,8 +682,15 @@ export const connectorDetails = { }, }, SaveCardUseNo3DSAutoCaptureOffSession: { + Configs: { + CONNECTOR_CREDENTIAL: { + specName: ["connectorAgnostic"], + value: "connector_2", + }, + }, Request: { payment_method: "card", + payment_method_type: "debit", payment_method_data: { card: successfulNo3DSCardDetails, }, @@ -497,15 +735,19 @@ export const connectorDetails = { }, }, SaveCardConfirmAutoCaptureOffSession: { + Configs: { + CONNECTOR_CREDENTIAL: { + specName: ["connectorAgnostic"], + value: "connector_2", + }, + }, Request: { setup_future_usage: "off_session", }, Response: { status: 200, body: { - error_code: "No error code", - error_message: - "You cannot confirm with `off_session=true` when `setup_future_usage` is also set on the PaymentIntent. The customer needs to be on-session to perform the steps which may be required to set up the PaymentMethod for future usage. Please confirm this PaymentIntent with your customer on-session.", + status: "succeeded", }, }, }, @@ -516,9 +758,19 @@ export const connectorDetails = { Response: { status: 200, body: { - error_code: "No error code", - error_message: - "You cannot confirm with `off_session=true` when `setup_future_usage` is also set on the PaymentIntent. The customer needs to be on-session to perform the steps which may be required to set up the PaymentMethod for future usage. Please confirm this PaymentIntent with your customer on-session.", + status: "requires_capture", + }, + }, + }, + SaveCardConfirmAutoCaptureOffSessionWithoutBilling: { + Request: { + setup_future_usage: "off_session", + billing: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", }, }, }, @@ -794,4 +1046,15 @@ export const connectorDetails = { }, }, }, + pm_list: { + PmListResponse: { + PmListNull: { + payment_methods: [], + }, + pmListDynamicFieldWithoutBilling: requiredFields, + pmListDynamicFieldWithBilling: requiredFields, + pmListDynamicFieldWithNames: requiredFields, + pmListDynamicFieldWithEmail: requiredFields, + }, + }, }; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Trustpay.js b/cypress-tests/cypress/e2e/PaymentUtils/Trustpay.js index c209fe5a6dcb..6c4db9df329b 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Trustpay.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Trustpay.js @@ -3,7 +3,7 @@ import { getCustomExchange } from "./Commons"; const successfulNo3DSCardDetails = { card_number: "4200000000000000", card_exp_month: "10", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "123", }; @@ -36,6 +36,12 @@ const multiUseMandateData = { export const connectorDetails = { card_pm: { PaymentIntent: getCustomExchange({ + Configs: { + CONNECTOR_CREDENTIAL: { + specName: ["refundPayment", "syncRefund"], + value: "connector_2", + }, + }, Request: { currency: "USD", customer_acceptance: null, @@ -48,7 +54,47 @@ export const connectorDetails = { }, }, }), + PaymentIntentWithShippingCost: { + Request: { + currency: "USD", + shipping_cost: 50, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + shipping_cost: 50, + amount: 6500, + }, + }, + }, + PaymentConfirmWithShippingCost: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + shipping_cost: 50, + amount_received: 6550, + amount: 6500, + net_amount: 6550, + }, + }, + }, "3DSAutoCapture": { + Configs: { + CONNECTOR_CREDENTIAL: { + specName: ["refundPayment", "syncRefund"], + value: "connector_2", + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -66,6 +112,12 @@ export const connectorDetails = { }, }, No3DSAutoCapture: { + Configs: { + CONNECTOR_CREDENTIAL: { + specName: ["refundPayment", "syncRefund"], + value: "connector_2", + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -83,6 +135,12 @@ export const connectorDetails = { }, }, Capture: { + Configs: { + CONNECTOR_CREDENTIAL: { + specName: ["refundPayment", "syncRefund"], + value: "connector_2", + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -104,6 +162,16 @@ export const connectorDetails = { }, }, PartialCapture: { + Configs: { + CONNECTOR_CREDENTIAL: { + specName: ["refundPayment", "syncRefund"], + value: "connector_2", + }, + DELAY: { + STATUS: true, + TIMEOUT: 15000, + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -138,6 +206,12 @@ export const connectorDetails = { }, }, Refund: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 15000, + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -154,6 +228,12 @@ export const connectorDetails = { }, }, PartialRefund: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 15000, + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -165,12 +245,18 @@ export const connectorDetails = { Response: { status: 200, body: { - error_code: "1", - error_message: "transaction declined (invalid amount)", + reason: "FRAUD", + status: "succeeded", }, }, }, SyncRefund: { + Configs: { + DELAY: { + STATUS: true, + TIMEOUT: 15000, + }, + }, Request: { payment_method: "card", payment_method_data: { @@ -214,6 +300,40 @@ export const connectorDetails = { }, }, }, + ZeroAuthPaymentIntent: { + Request: { + amount: 0, + setup_future_usage: "off_session", + currency: "USD", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + ZeroAuthConfirmPayment: { + Request: { + payment_type: "setup_mandate", + payment_method: "card", + payment_method_type: "credit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + }, + Response: { + status: 501, + body: { + error: { + type: "invalid_request", + message: "Setup Mandate flow for Trustpay is not implemented", + code: "IR_00", + }, + }, + }, + }, SaveCardUseNo3DSAutoCapture: { Request: { payment_method: "card", diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Utils.js b/cypress-tests/cypress/e2e/PaymentUtils/Utils.js index 03fc7fe9724e..72bd6451347a 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Utils.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Utils.js @@ -1,52 +1,75 @@ +import { execConfig, validateConfig } from "../../utils/featureFlags.js"; + import { connectorDetails as adyenConnectorDetails } from "./Adyen.js"; import { connectorDetails as bankOfAmericaConnectorDetails } from "./BankOfAmerica.js"; import { connectorDetails as bluesnapConnectorDetails } from "./Bluesnap.js"; +import { connectorDetails as checkoutConnectorDetails } from "./Checkout.js"; import { connectorDetails as CommonConnectorDetails, updateDefaultStatusCode, } from "./Commons.js"; import { connectorDetails as cybersourceConnectorDetails } from "./Cybersource.js"; import { connectorDetails as datatransConnectorDetails } from "./Datatrans.js"; +import { connectorDetails as elavonConnectorDetails } from "./Elavon.js"; import { connectorDetails as fiservemeaConnectorDetails } from "./Fiservemea.js"; +import { connectorDetails as fiuuConnectorDetails } from "./Fiuu.js"; import { connectorDetails as iatapayConnectorDetails } from "./Iatapay.js"; import { connectorDetails as itaubankConnectorDetails } from "./ItauBank.js"; +import { connectorDetails as jpmorganConnectorDetails } from "./Jpmorgan.js"; +import { connectorDetails as nexixpayConnectorDetails } from "./Nexixpay.js"; import { connectorDetails as nmiConnectorDetails } from "./Nmi.js"; +import { connectorDetails as noonConnectorDetails } from "./Noon.js"; import { connectorDetails as novalnetConnectorDetails } from "./Novalnet.js"; import { connectorDetails as payboxConnectorDetails } from "./Paybox.js"; import { connectorDetails as paypalConnectorDetails } from "./Paypal.js"; import { connectorDetails as stripeConnectorDetails } from "./Stripe.js"; import { connectorDetails as trustpayConnectorDetails } from "./Trustpay.js"; import { connectorDetails as wellsfargoConnectorDetails } from "./WellsFargo.js"; -import { connectorDetails as fiuuConnectorDetails } from "./Fiuu.js"; +import { connectorDetails as worldpayConnectorDetails } from "./WorldPay.js"; const connectorDetails = { adyen: adyenConnectorDetails, bankofamerica: bankOfAmericaConnectorDetails, bluesnap: bluesnapConnectorDetails, + checkout: checkoutConnectorDetails, commons: CommonConnectorDetails, cybersource: cybersourceConnectorDetails, fiservemea: fiservemeaConnectorDetails, iatapay: iatapayConnectorDetails, itaubank: itaubankConnectorDetails, + jpmorgan: jpmorganConnectorDetails, + nexixpay: nexixpayConnectorDetails, nmi: nmiConnectorDetails, novalnet: novalnetConnectorDetails, paybox: payboxConnectorDetails, paypal: paypalConnectorDetails, stripe: stripeConnectorDetails, + elavon: elavonConnectorDetails, trustpay: trustpayConnectorDetails, datatrans: datatransConnectorDetails, wellsfargo: wellsfargoConnectorDetails, fiuu: fiuuConnectorDetails, + worldpay: worldpayConnectorDetails, + noon: noonConnectorDetails, }; export default function getConnectorDetails(connectorId) { - let x = mergeDetails(connectorId); + const x = mergeDetails(connectorId); return x; } +export function getConnectorFlowDetails(connectorData, commonData, key) { + const data = + connectorData[key] === undefined ? commonData[key] : connectorData[key]; + return data; +} + function mergeDetails(connectorId) { - const connectorData = getValueByKey(connectorDetails, connectorId); - const fallbackData = getValueByKey(connectorDetails, "commons"); + const connectorData = getValueByKey( + connectorDetails, + connectorId + ).authDetails; + const fallbackData = getValueByKey(connectorDetails, "commons").authDetails; // Merge data, prioritizing connectorData and filling missing data from fallbackData const mergedDetails = mergeConnectorDetails(connectorData, fallbackData); return mergedDetails; @@ -79,26 +102,64 @@ function mergeConnectorDetails(source, fallback) { return merged; } -export function getValueByKey(jsonObject, key) { +export function handleMultipleConnectors(keys) { + return { + MULTIPLE_CONNECTORS: { + status: true, + count: keys.length, + }, + }; +} + +export function getValueByKey(jsonObject, key, keyNumber = 0) { const data = typeof jsonObject === "string" ? JSON.parse(jsonObject) : jsonObject; if (data && typeof data === "object" && key in data) { - return data[key]; - } else { - return null; + // Connector object has multiple keys + if (typeof data[key].connector_account_details === "undefined") { + const keys = Object.keys(data[key]); + + for (let i = keyNumber; i < keys.length; i++) { + const currentItem = data[key][keys[i]]; + + if ( + Object.prototype.hasOwnProperty.call( + currentItem, + "connector_account_details" + ) + ) { + // Return state update instead of setting directly + return { + authDetails: currentItem, + stateUpdate: handleMultipleConnectors(keys), + }; + } + } + } + return { + authDetails: data[key], + stateUpdate: null, + }; } + return { + authDetails: null, + stateUpdate: null, + }; } -export const should_continue_further = (res_data) => { - if (res_data.trigger_skip !== undefined) { - return !res_data.trigger_skip; +export const should_continue_further = (data) => { + const resData = data.Response || {}; + const configData = validateConfig(data.Configs) || {}; + + if (typeof configData?.TRIGGER_SKIP !== "undefined") { + return !configData.TRIGGER_SKIP; } if ( - res_data.body.error !== undefined || - res_data.body.error_code !== undefined || - res_data.body.error_message !== undefined + typeof resData.body.error !== "undefined" || + typeof resData.body.error_code !== "undefined" || + typeof resData.body.error_message !== "undefined" ) { return false; } else { @@ -123,9 +184,128 @@ export function defaultErrorHandler(response, response_data) { if (typeof response.body.error === "object") { for (const key in response_data.body.error) { - expect(response_data.body.error[key]).to.equal(response.body.error[key]); + // Check if the error message is a Json deserialize error + const apiResponseContent = response.body.error[key]; + const expectedContent = response_data.body.error[key]; + if ( + typeof apiResponseContent === "string" && + apiResponseContent.includes("Json deserialize error") + ) { + expect(apiResponseContent).to.include(expectedContent); + } else { + expect(apiResponseContent).to.equal(expectedContent); + } } - } else if (typeof response.body.error === "string") { - expect(response.body.error).to.include(response_data.body.error); } } + +export function extractIntegerAtEnd(str) { + // Match one or more digits at the end of the string + const match = str.match(/(\d+)$/); + return match ? parseInt(match[0], 10) : 0; +} + +// Common helper function to check if operation should proceed +function shouldProceedWithOperation(multipleConnector, multipleConnectors) { + return !( + multipleConnector?.nextConnector === true && + (multipleConnectors?.status === false || + typeof multipleConnectors === "undefined") + ); +} + +// Helper to get connector configuration +function getConnectorConfig( + globalState, + multipleConnector = { nextConnector: false } +) { + const multipleConnectors = globalState.get("MULTIPLE_CONNECTORS"); + const mcaConfig = getConnectorDetails(globalState.get("connectorId")); + + return { + config: { + CONNECTOR_CREDENTIAL: + multipleConnector?.nextConnector && multipleConnectors?.status + ? multipleConnector + : mcaConfig?.multi_credential_config || multipleConnector, + }, + multipleConnectors, + }; +} + +// Simplified createBusinessProfile +export function createBusinessProfile( + createBusinessProfileBody, + globalState, + multipleConnector = { nextConnector: false } +) { + const { config, multipleConnectors } = getConnectorConfig( + globalState, + multipleConnector + ); + const { profilePrefix } = execConfig(config); + + if (shouldProceedWithOperation(multipleConnector, multipleConnectors)) { + cy.createBusinessProfileTest( + createBusinessProfileBody, + globalState, + profilePrefix + ); + } +} + +// Simplified createMerchantConnectorAccount +export function createMerchantConnectorAccount( + paymentType, + createMerchantConnectorAccountBody, + globalState, + paymentMethodsEnabled, + multipleConnector = { nextConnector: false } +) { + const { config, multipleConnectors } = getConnectorConfig( + globalState, + multipleConnector + ); + const { profilePrefix, merchantConnectorPrefix } = execConfig(config); + + if (shouldProceedWithOperation(multipleConnector, multipleConnectors)) { + cy.createConnectorCallTest( + paymentType, + createMerchantConnectorAccountBody, + paymentMethodsEnabled, + globalState, + profilePrefix, + merchantConnectorPrefix + ); + } +} + +export function updateBusinessProfile( + updateBusinessProfileBody, + is_connector_agnostic_enabled, + collect_billing_address_from_wallet_connector, + collect_shipping_address_from_wallet_connector, + always_collect_billing_address_from_wallet_connector, + always_collect_shipping_address_from_wallet_connector, + globalState +) { + const multipleConnectors = globalState.get("MULTIPLE_CONNECTORS"); + cy.log(`MULTIPLE_CONNECTORS: ${JSON.stringify(multipleConnectors)}`); + + // Get MCA config + const mcaConfig = getConnectorDetails(globalState.get("connectorId")); + const { profilePrefix } = execConfig({ + CONNECTOR_CREDENTIAL: mcaConfig?.multi_credential_config, + }); + + cy.UpdateBusinessProfileTest( + updateBusinessProfileBody, + is_connector_agnostic_enabled, + collect_billing_address_from_wallet_connector, + collect_shipping_address_from_wallet_connector, + always_collect_billing_address_from_wallet_connector, + always_collect_shipping_address_from_wallet_connector, + globalState, + profilePrefix + ); +} diff --git a/cypress-tests/cypress/e2e/PaymentUtils/WellsFargo.js b/cypress-tests/cypress/e2e/PaymentUtils/WellsFargo.js index b25a6dc7e8bf..38a5b637040a 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/WellsFargo.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/WellsFargo.js @@ -1,7 +1,7 @@ const successfulNo3DSCardDetails = { card_number: "4242424242424242", card_exp_month: "01", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "123", }; @@ -9,7 +9,7 @@ const successfulNo3DSCardDetails = { const successfulThreeDSTestCardDetails = { card_number: "4000000000001091", card_exp_month: "01", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "joseph Doe", card_cvc: "123", }; @@ -68,6 +68,9 @@ export const connectorDetails = { }, }, "3DSManualCapture": { + Configs: { + TRIGGER_SKIP: true, + }, Request: { payment_method: "card", payment_method_data: { @@ -79,13 +82,15 @@ export const connectorDetails = { }, Response: { status: 200, - trigger_skip: true, body: { status: "requires_capture", }, }, }, "3DSAutoCapture": { + Configs: { + TRIGGER_SKIP: true, + }, Request: { payment_method: "card", payment_method_data: { @@ -97,7 +102,6 @@ export const connectorDetails = { }, Response: { status: 200, - trigger_skip: true, body: { status: "requires_customer_action", @@ -227,6 +231,9 @@ export const connectorDetails = { }, }, MandateSingleUse3DSAutoCapture: { + Configs: { + TRIGGER_SKIP: true, + }, Request: { payment_method: "card", payment_method_data: { @@ -237,7 +244,6 @@ export const connectorDetails = { }, Response: { status: 200, - trigger_skip: true, body: { status: "succeeded", @@ -245,6 +251,9 @@ export const connectorDetails = { }, }, MandateSingleUse3DSManualCapture: { + Configs: { + TRIGGER_SKIP: true, + }, Request: { payment_method: "card", payment_method_data: { @@ -255,7 +264,6 @@ export const connectorDetails = { }, Response: { status: 200, - trigger_skip: true, body: { status: "requires_customer_action", @@ -327,6 +335,9 @@ export const connectorDetails = { }, }, MandateMultiUse3DSAutoCapture: { + Configs: { + TRIGGER_SKIP: true, + }, Request: { payment_method: "card", payment_method_data: { @@ -337,7 +348,6 @@ export const connectorDetails = { }, Response: { status: 200, - trigger_skip: true, body: { status: "requires_capture", @@ -345,6 +355,9 @@ export const connectorDetails = { }, }, MandateMultiUse3DSManualCapture: { + Configs: { + TRIGGER_SKIP: true, + }, Request: { payment_method: "card", payment_method_data: { @@ -355,13 +368,30 @@ export const connectorDetails = { }, Response: { status: 200, - trigger_skip: true, body: { status: "requires_capture", }, }, }, + MITAutoCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MITManualCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, ZeroAuthMandate: { Request: { payment_method: "card", @@ -475,6 +505,9 @@ export const connectorDetails = { }, }, PaymentMethodIdMandate3DSAutoCapture: { + Configs: { + TRIGGER_SKIP: true, + }, Request: { payment_method: "card", payment_method_data: { @@ -494,7 +527,6 @@ export const connectorDetails = { }, Response: { status: 200, - trigger_skip: true, body: { status: "requires_customer_action", }, diff --git a/cypress-tests/cypress/e2e/PaymentUtils/WorldPay.js b/cypress-tests/cypress/e2e/PaymentUtils/WorldPay.js new file mode 100644 index 000000000000..a58bbb22f81d --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentUtils/WorldPay.js @@ -0,0 +1,641 @@ +import { getCustomExchange } from "./Commons"; + +const billing = { + address: { + line1: "1467", + line2: "Harrison Street", + line3: "Harrison Street", + city: "San Fransico", + state: "CA", + zip: "94122", + country: "US", + first_name: "John", + last_name: "Doe", + }, +}; + +const 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", +}; + +const successfulNoThreeDsCardDetailsRequest = { + card_number: "4242424242424242", + card_exp_month: "10", + card_exp_year: "30", + card_holder_name: "morino", + card_cvc: "737", +}; + +const successfulThreeDsTestCardDetailsRequest = { + card_number: "4000000000001091", + card_exp_month: "10", + card_exp_year: "30", + card_holder_name: "morino", + card_cvc: "737", +}; + +const paymentMethodDataNoThreeDsResponse = { + card: { + last4: "4242", + card_type: "CREDIT", + card_network: "Visa", + card_issuer: "STRIPE PAYMENTS UK LIMITED", + card_issuing_country: "UNITEDKINGDOM", + card_isin: "424242", + card_extended_bin: null, + card_exp_month: "10", + card_exp_year: "30", + card_holder_name: null, + payment_checks: null, + authentication_data: null, + }, + billing: null, +}; + +const payment_method_data_3ds = { + card: { + last4: "1091", + card_type: "CREDIT", + card_network: "Visa", + card_issuer: "INTL HDQTRS-CENTER OWNED", + card_issuing_country: "UNITEDSTATES", + card_isin: "400000", + card_extended_bin: null, + card_exp_month: "10", + card_exp_year: "30", + card_holder_name: null, + payment_checks: null, + authentication_data: null, + }, + billing: null, +}; + +const customerAcceptance = { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, +}; + +const singleUseMandateData = { + customer_acceptance: customerAcceptance, + mandate_type: { + single_use: { + amount: 8000, + currency: "USD", + }, + }, +}; + +export const connectorDetails = { + card_pm: { + PaymentIntent: { + Request: { + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "on_session", + }, + }, + }, + No3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_type: "debit", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + billing: billing, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + payment_method: "card", + payment_method_type: "credit", + attempt_count: 1, + payment_method_data: paymentMethodDataNoThreeDsResponse, + }, + }, + }, + No3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_type: "debit", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + payment_method: "card", + payment_method_type: "credit", + attempt_count: 1, + payment_method_data: paymentMethodDataNoThreeDsResponse, + }, + }, + }, + Capture: { + Request: { + payment_method: "card", + payment_method_type: "debit", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "succeeded", + amount: 6500, + amount_capturable: 0, + }, + }, + }, + PartialCapture: { + Request: { + payment_method: "card", + payment_method_type: "debit", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + currency: "USD", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "partially_captured", + amount: 6500, + amount_capturable: 0, + }, + }, + }, + Void: getCustomExchange({ + Request: {}, + Response: { + status: 200, + body: { + status: "cancelled", + }, + }, + ResponseCustom: { + body: { + type: "invalid_request", + message: + "You cannot cancel this payment because it has status processing", + code: "IR_16", + }, + }, + }), + VoidAfterConfirm: { + Request: {}, + Response: { + status: 200, + body: { + status: "processing", + }, + }, + }, + SaveCardUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + currency: "USD", + setup_future_usage: "on_session", + customer_acceptance: customerAcceptance, + }, + Response: { + body: { + status: "requires_capture", + }, + }, + }, + SaveCardUseNo3DSManualCaptureOffSession: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + setup_future_usage: "off_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + SaveCardConfirmManualCaptureOffSession: { + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + SaveCardUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + currency: "USD", + setup_future_usage: "on_session", + browser_info, + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardUseNo3DSAutoCaptureOffSession: { + Request: { + payment_method: "card", + payment_method_type: "debit", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + setup_future_usage: "off_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardConfirmAutoCaptureOffSession: { + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + "3DSManualCapture": { + Request: { + payment_method: "card", + payment_method_type: "debit", + payment_method_data: { + card: successfulThreeDsTestCardDetailsRequest, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + browser_info, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + setup_future_usage: "on_session", + payment_method_data: payment_method_data_3ds, + }, + }, + }, + "3DSAutoCapture": { + Request: { + payment_method: "card", + payment_method_type: "debit", + payment_method_data: { + card: successfulThreeDsTestCardDetailsRequest, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + browser_info, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + setup_future_usage: "on_session", + payment_method_data: payment_method_data_3ds, + }, + }, + }, + CaptureCapturedAmount: { + Request: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + currency: "EUR", + 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 succeeded. The expected state is requires_capture, partially_captured_and_capturable, processing", + code: "IR_14", + }, + }, + }, + }, + ConfirmSuccessfulPayment: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + customer_acceptance: null, + }, + Response: { + status: 400, + body: { + error: { + type: "invalid_request", + message: + "You cannot confirm this payment because it has status succeeded", + code: "IR_16", + }, + }, + }, + }, + Refund: { + Request: {}, + Response: { + body: { + status: "succeeded", + }, + }, + }, + PartialRefund: { + Request: {}, + Response: { + body: { + status: "succeeded", + }, + }, + }, + manualPaymentRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + currency: "USD", + }, + Response: { + body: { + status: "succeeded", + }, + }, + }, + manualPaymentPartialRefund: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + currency: "USD", + }, + Response: { + body: { + status: "succeeded", + }, + }, + }, + SyncRefund: { + Request: {}, + Response: { + body: { + status: "succeeded", + }, + }, + }, + MandateSingleUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MandateSingleUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + MandateMultiUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MandateMultiUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + ZeroAuthMandate: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + trigger_skip: true, + status: 200, + body: { + error_code: "internalErrorOccurred", + error_message: + "We cannot currently process your request. Please contact support.", + status: "failed", + payment_method_id: null, + }, + }, + }, + ZeroAuthPaymentIntent: { + Request: { + amount: 0, + setup_future_usage: "off_session", + currency: "USD", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + ZeroAuthConfirmPayment: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + trigger_skip: true, + status: 200, + body: { + error_code: "internalErrorOccurred", + error_message: + "We cannot currently process your request. Please contact support.", + status: "failed", + payment_method_id: null, + }, + }, + }, + PaymentMethodIdMandateNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + currency: "USD", + mandate_data: null, + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + PaymentMethodIdMandateNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + currency: "USD", + mandate_data: null, + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + PaymentMethodIdMandate3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDsTestCardDetailsRequest, + }, + currency: "USD", + mandate_data: null, + authentication_type: "three_ds", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + PaymentMethodIdMandate3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDsTestCardDetailsRequest, + }, + mandate_data: null, + authentication_type: "three_ds", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + }, +}; diff --git a/cypress-tests/cypress/e2e/PayoutTest/00003-CardTest.cy.js b/cypress-tests/cypress/e2e/PayoutTest/00003-CardTest.cy.js index 2caa6030c536..fd9b9d9f6e27 100644 --- a/cypress-tests/cypress/e2e/PayoutTest/00003-CardTest.cy.js +++ b/cypress-tests/cypress/e2e/PayoutTest/00003-CardTest.cy.js @@ -5,7 +5,7 @@ import * as utils from "../PayoutUtils/Utils"; let globalState; describe("[Payout] Cards", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails before("seed global state", () => { cy.task("getGlobalState").then((state) => { @@ -13,7 +13,7 @@ describe("[Payout] Cards", () => { // Check if the connector supports card payouts (based on the connector configuration in creds) if (!globalState.get("payoutsExecution")) { - should_continue = false; + shouldContinue = false; } }); }); @@ -23,37 +23,34 @@ describe("[Payout] Cards", () => { }); beforeEach(function () { - if (!should_continue) { + if (!shouldContinue) { this.skip(); } }); context("Payout Card with Auto Fulfill", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails beforeEach(function () { - if (!should_continue) { + if (!shouldContinue) { this.skip(); } }); it("confirm-payout-call-with-auto-fulfill-test", () => { - let data = utils.getConnectorDetails(globalState.get("connectorId"))[ + const data = utils.getConnectorDetails(globalState.get("connectorId"))[ "card_pm" ]["Fulfill"]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createConfirmPayoutTest( fixtures.createPayoutBody, - req_data, - res_data, + data, true, true, globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); it("retrieve-payout-call-test", () => { @@ -62,41 +59,36 @@ describe("[Payout] Cards", () => { }); context("Payout Card with Manual Fulfill - Create Confirm", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails beforeEach(function () { - if (!should_continue) { + if (!shouldContinue) { this.skip(); } }); it("confirm-payout-call-with-manual-fulfill-test", () => { - let data = utils.getConnectorDetails(globalState.get("connectorId"))[ + const data = utils.getConnectorDetails(globalState.get("connectorId"))[ "card_pm" ]["Confirm"]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createConfirmPayoutTest( fixtures.createPayoutBody, - req_data, - res_data, + data, true, false, globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); it("fulfill-payout-call-test", () => { - let data = utils.getConnectorDetails(globalState.get("connectorId"))[ + const data = utils.getConnectorDetails(globalState.get("connectorId"))[ "card_pm" ]["Fulfill"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.fulfillPayoutCallTest({}, req_data, res_data, globalState); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + cy.fulfillPayoutCallTest({}, data, globalState); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); it("retrieve-payout-call-test", () => { diff --git a/cypress-tests/cypress/e2e/PayoutTest/00004-BankTransfer.cy.js b/cypress-tests/cypress/e2e/PayoutTest/00004-BankTransfer.cy.js index 887152c3a6f5..0cb4508ded82 100644 --- a/cypress-tests/cypress/e2e/PayoutTest/00004-BankTransfer.cy.js +++ b/cypress-tests/cypress/e2e/PayoutTest/00004-BankTransfer.cy.js @@ -6,7 +6,7 @@ let globalState; // TODO: Add test for Bank Transfer - ACH describe.skip("[Payout] [Bank Transfer - ACH]", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails before("seed global state", () => { cy.task("getGlobalState").then((state) => { @@ -14,7 +14,7 @@ describe.skip("[Payout] [Bank Transfer - ACH]", () => { // Check if the connector supports card payouts (based on the connector configuration in creds) if (!globalState.get("payoutsExecution")) { - should_continue = false; + shouldContinue = false; } }); }); @@ -24,7 +24,7 @@ describe.skip("[Payout] [Bank Transfer - ACH]", () => { }); beforeEach(function () { - if (!should_continue) { + if (!shouldContinue) { this.skip(); } }); @@ -32,7 +32,7 @@ describe.skip("[Payout] [Bank Transfer - ACH]", () => { // TODO: Add test for Bank Transfer - BACS describe.skip("[Payout] [Bank Transfer - BACS]", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails before("seed global state", () => { cy.task("getGlobalState").then((state) => { @@ -40,7 +40,7 @@ describe.skip("[Payout] [Bank Transfer - BACS]", () => { // Check if the connector supports card payouts (based on the connector configuration in creds) if (!globalState.get("payoutsExecution")) { - should_continue = false; + shouldContinue = false; } }); }); @@ -50,14 +50,14 @@ describe.skip("[Payout] [Bank Transfer - BACS]", () => { }); beforeEach(function () { - if (!should_continue) { + if (!shouldContinue) { this.skip(); } }); }); describe("[Payout] [Bank Transfer - SEPA]", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails before("seed global state", () => { cy.task("getGlobalState").then((state) => { @@ -65,7 +65,7 @@ describe("[Payout] [Bank Transfer - SEPA]", () => { // Check if the connector supports card payouts (based on the connector configuration in creds) if (!globalState.get("payoutsExecution")) { - should_continue = false; + shouldContinue = false; } }); }); @@ -75,37 +75,33 @@ describe("[Payout] [Bank Transfer - SEPA]", () => { }); beforeEach(function () { - if (!should_continue) { + if (!shouldContinue) { this.skip(); } }); context("[Payout] [Bank transfer - SEPA] Auto Fulfill", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails beforeEach(function () { - if (!should_continue) { + if (!shouldContinue) { this.skip(); } }); it("confirm-payout-call-with-auto-fulfill-test", () => { - let data = utils.getConnectorDetails(globalState.get("connectorId"))[ + const data = utils.getConnectorDetails(globalState.get("connectorId"))[ "bank_transfer_pm" ]["sepa"]["Fulfill"]; - let req_data = data["Request"]; - let res_data = data["Response"]; cy.createConfirmPayoutTest( fixtures.createPayoutBody, - req_data, - res_data, + data, true, true, globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); it("retrieve-payout-call-test", () => { @@ -114,41 +110,36 @@ describe("[Payout] [Bank Transfer - SEPA]", () => { }); context("[Payout] [Bank transfer - SEPA] Manual Fulfill", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails beforeEach(function () { - if (!should_continue) { + if (!shouldContinue) { this.skip(); } }); it("confirm-payout-call-with-manual-fulfill-test", () => { - let data = utils.getConnectorDetails(globalState.get("connectorId"))[ + const data = utils.getConnectorDetails(globalState.get("connectorId"))[ "bank_transfer_pm" ]["sepa"]["Confirm"]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createConfirmPayoutTest( fixtures.createPayoutBody, - req_data, - res_data, + data, true, false, globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); it("fulfill-payout-call-test", () => { - let data = utils.getConnectorDetails(globalState.get("connectorId"))[ + const data = utils.getConnectorDetails(globalState.get("connectorId"))[ "bank_transfer_pm" ]["sepa"]["Fulfill"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.fulfillPayoutCallTest({}, req_data, res_data, globalState); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + cy.fulfillPayoutCallTest({}, data, globalState); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); it("retrieve-payout-call-test", () => { diff --git a/cypress-tests/cypress/e2e/PayoutTest/00005-SavePayout.cy.js b/cypress-tests/cypress/e2e/PayoutTest/00005-SavePayout.cy.js index edcb89d06fd4..37dc1c2cdf0f 100644 --- a/cypress-tests/cypress/e2e/PayoutTest/00005-SavePayout.cy.js +++ b/cypress-tests/cypress/e2e/PayoutTest/00005-SavePayout.cy.js @@ -6,7 +6,7 @@ let globalState; let payoutBody; describe("[Payout] Saved Card", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails before("seed global state", () => { cy.task("getGlobalState").then((state) => { @@ -14,7 +14,7 @@ describe("[Payout] Saved Card", () => { // Check if the connector supports card payouts (based on the connector configuration in creds) if (!globalState.get("payoutsExecution")) { - should_continue = false; + shouldContinue = false; } }); }); @@ -24,13 +24,13 @@ describe("[Payout] Saved Card", () => { }); beforeEach(function () { - if (!should_continue) { + if (!shouldContinue) { this.skip(); } }); context("[Payout] [Card] Onboard customer prior to transaction", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails // This is needed to get customer payment methods beforeEach("seed global state", () => { @@ -42,12 +42,11 @@ describe("[Payout] Saved Card", () => { }); it("create payment method", () => { - let data = utils.getConnectorDetails(globalState.get("connectorId"))[ + const data = utils.getConnectorDetails(globalState.get("connectorId"))[ "card_pm" ]["SavePayoutMethod"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createPaymentMethodTest(globalState, req_data, res_data); + + cy.createPaymentMethodTest(globalState, data); }); it("list customer payment methods", () => { @@ -55,22 +54,19 @@ describe("[Payout] Saved Card", () => { }); it("confirm-payout-call-with-auto-fulfill-test", () => { - let data = utils.getConnectorDetails(globalState.get("connectorId"))[ + const data = utils.getConnectorDetails(globalState.get("connectorId"))[ "card_pm" ]["Token"]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createConfirmWithTokenPayoutTest( payoutBody, - req_data, - res_data, + data, true, true, globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); it("retrieve-payout-call-test", () => { @@ -81,10 +77,10 @@ describe("[Payout] Saved Card", () => { context( "[Payout] [Card] Save payment method after successful transaction", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails beforeEach(function () { - if (!should_continue) { + if (!shouldContinue) { this.skip(); } }); @@ -94,22 +90,14 @@ describe("[Payout] Saved Card", () => { }); it("confirm-payout-call-with-auto-fulfill-test", () => { - let data = utils.getConnectorDetails(globalState.get("connectorId"))[ + const data = utils.getConnectorDetails(globalState.get("connectorId"))[ "card_pm" ]["Fulfill"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createConfirmPayoutTest( - payoutBody, - req_data, - res_data, - true, - true, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + cy.createConfirmPayoutTest(payoutBody, data, true, true, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); }); it("list customer payment methods", () => { @@ -117,22 +105,20 @@ describe("[Payout] Saved Card", () => { }); it("confirm-payout-call-with-auto-fulfill-test", () => { - let data = utils.getConnectorDetails(globalState.get("connectorId"))[ + const data = utils.getConnectorDetails(globalState.get("connectorId"))[ "card_pm" ]["Token"]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createConfirmWithTokenPayoutTest( payoutBody, - req_data, - res_data, + data, true, true, globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); }); it("retrieve-payout-call-test", () => { @@ -143,7 +129,7 @@ describe("[Payout] Saved Card", () => { }); describe("[Payout] Saved Bank transfer", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails before("seed global state", () => { cy.task("getGlobalState").then((state) => { @@ -151,7 +137,7 @@ describe("[Payout] Saved Bank transfer", () => { // Check if the connector supports card payouts (based on the connector configuration in creds) if (!globalState.get("payoutsExecution")) { - should_continue = false; + shouldContinue = false; } }); }); @@ -161,7 +147,7 @@ describe("[Payout] Saved Bank transfer", () => { }); beforeEach(function () { - if (!should_continue) { + if (!shouldContinue) { this.skip(); } }); @@ -169,7 +155,7 @@ describe("[Payout] Saved Bank transfer", () => { context( "[Payout] [Bank Transfer] Onboard Customer Prior to Transaction", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails beforeEach("reset payoutBody", () => { payoutBody = Cypress._.cloneDeep(fixtures.createPayoutBody); }); @@ -179,12 +165,11 @@ describe("[Payout] Saved Bank transfer", () => { }); it("create payment method", () => { - let data = utils.getConnectorDetails(globalState.get("connectorId"))[ + const data = utils.getConnectorDetails(globalState.get("connectorId"))[ "bank_transfer_pm" ]["sepa"]["SavePayoutMethod"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createPaymentMethodTest(globalState, req_data, res_data); + + cy.createPaymentMethodTest(globalState, data); }); it("list customer payment methods", () => { @@ -192,22 +177,20 @@ describe("[Payout] Saved Bank transfer", () => { }); it("[Payout] [Bank transfer] [SEPA] Fulfill using Token", () => { - let data = utils.getConnectorDetails(globalState.get("connectorId"))[ + const data = utils.getConnectorDetails(globalState.get("connectorId"))[ "bank_transfer_pm" ]["sepa"]["Token"]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createConfirmWithTokenPayoutTest( payoutBody, - req_data, - res_data, + data, true, true, globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); }); it("retrieve-payout-call-test", () => { @@ -219,10 +202,10 @@ describe("[Payout] Saved Bank transfer", () => { context( "[Payout] [Bank Transfer] Save payment method after successful transaction", () => { - let should_continue = true; // variable that will be used to skip tests if a previous test fails + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails beforeEach(function () { - if (!should_continue) { + if (!shouldContinue) { this.skip(); } }); @@ -232,22 +215,14 @@ describe("[Payout] Saved Bank transfer", () => { }); it("confirm-payout-call-with-auto-fulfill-test", () => { - let data = utils.getConnectorDetails(globalState.get("connectorId"))[ + const data = utils.getConnectorDetails(globalState.get("connectorId"))[ "bank_transfer_pm" ]["sepa"]["Fulfill"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.createConfirmPayoutTest( - payoutBody, - req_data, - res_data, - true, - true, - globalState - ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + cy.createConfirmPayoutTest(payoutBody, data, true, true, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); }); it("list customer payment methods", () => { @@ -255,22 +230,20 @@ describe("[Payout] Saved Bank transfer", () => { }); it("[Payout] [Bank transfer] [SEPA] Fulfill using Token", () => { - let data = utils.getConnectorDetails(globalState.get("connectorId"))[ + const data = utils.getConnectorDetails(globalState.get("connectorId"))[ "bank_transfer_pm" ]["sepa"]["Token"]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createConfirmWithTokenPayoutTest( payoutBody, - req_data, - res_data, + data, true, true, globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); }); it("retrieve-payout-call-test", () => { diff --git a/cypress-tests/cypress/e2e/PayoutUtils/AdyenPlatform.js b/cypress-tests/cypress/e2e/PayoutUtils/AdyenPlatform.js index 75b45aac2046..0048daf45b88 100644 --- a/cypress-tests/cypress/e2e/PayoutUtils/AdyenPlatform.js +++ b/cypress-tests/cypress/e2e/PayoutUtils/AdyenPlatform.js @@ -71,7 +71,7 @@ export const connectorDetails = { Response: { status: 200, body: { - status: "success", + status: "initiated", payout_type: "bank", }, }, @@ -101,7 +101,7 @@ export const connectorDetails = { Response: { status: 200, body: { - status: "success", + status: "initiated", payout_type: "bank", }, }, diff --git a/cypress-tests/cypress/e2e/PayoutUtils/Commons.js b/cypress-tests/cypress/e2e/PayoutUtils/Commons.js index 5f8a580731e0..ecd85b051511 100644 --- a/cypress-tests/cypress/e2e/PayoutUtils/Commons.js +++ b/cypress-tests/cypress/e2e/PayoutUtils/Commons.js @@ -118,7 +118,6 @@ export const connectorDetails = { card: card_data, }, currency: "EUR", - payout_type: "card", }, Response: { status: 200, @@ -135,7 +134,6 @@ export const connectorDetails = { card: card_data, }, currency: "EUR", - payout_type: "card", }, Response: { status: 200, @@ -152,7 +150,6 @@ export const connectorDetails = { card: card_data, }, currency: "EUR", - payout_type: "card", }, }), SavePayoutMethod: getCustomExchange({ diff --git a/cypress-tests/cypress/e2e/PayoutUtils/Utils.js b/cypress-tests/cypress/e2e/PayoutUtils/Utils.js index d57aac2290d1..4cd122782f21 100644 --- a/cypress-tests/cypress/e2e/PayoutUtils/Utils.js +++ b/cypress-tests/cypress/e2e/PayoutUtils/Utils.js @@ -1,3 +1,5 @@ +import { validateConfig } from "../../utils/featureFlags.js"; + import { connectorDetails as adyenConnectorDetails } from "./Adyen.js"; import { connectorDetails as adyenPlatformConnectorDetails } from "./AdyenPlatform.js"; import { connectorDetails as CommonConnectorDetails } from "./Commons.js"; @@ -11,7 +13,7 @@ const connectorDetails = { }; export function getConnectorDetails(connectorId) { - let x = mergeDetails(connectorId); + const x = mergeDetails(connectorId); return x; } @@ -50,22 +52,52 @@ function mergeConnectorDetails(source, fallback) { return merged; } -function getValueByKey(jsonObject, key) { +export function getValueByKey(jsonObject, key) { const data = typeof jsonObject === "string" ? JSON.parse(jsonObject) : jsonObject; if (data && typeof data === "object" && key in data) { + // Connector object has multiple keys + if (typeof data[key].connector_account_details === "undefined") { + const keys = Object.keys(data[key]); + + for (let i = 0; i < keys.length; i++) { + const currentItem = data[key][keys[i]]; + + if ( + Object.prototype.hasOwnProperty.call( + currentItem, + "connector_account_details" + ) + ) { + Cypress.env("MULTIPLE_CONNECTORS", { + status: true, + count: keys.length, + }); + + return currentItem; + } + } + } + return data[key]; } else { return null; } } -export const should_continue_further = (res_data) => { +export const should_continue_further = (data) => { + const resData = data.Response || {}; + const configData = validateConfig(data.Configs) || {}; + + if (typeof configData?.TRIGGER_SKIP !== "undefined") { + return !configData.TRIGGER_SKIP; + } + if ( - res_data.body.error !== undefined || - res_data.body.error_code !== undefined || - res_data.body.error_message !== undefined + typeof resData.body.error !== "undefined" || + typeof resData.body.error_code !== "undefined" || + typeof resData.body.error_message !== "undefined" ) { return false; } else { diff --git a/cypress-tests/cypress/e2e/RoutingTest/00000-PriorityRouting.cy.js b/cypress-tests/cypress/e2e/RoutingTest/00000-PriorityRouting.cy.js index 3037a6eb99ec..22e4b3783af1 100644 --- a/cypress-tests/cypress/e2e/RoutingTest/00000-PriorityRouting.cy.js +++ b/cypress-tests/cypress/e2e/RoutingTest/00000-PriorityRouting.cy.js @@ -5,7 +5,7 @@ import * as utils from "../RoutingUtils/Utils"; let globalState; describe("Priority Based Routing Test", () => { - let should_continue = true; + let shouldContinue = true; context("Login", () => { before("seed global state", () => { @@ -52,11 +52,8 @@ describe("Priority Based Routing Test", () => { }); it("add-routing-config", () => { - let data = utils.getConnectorDetails("common")["priorityRouting"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - - let routing_data = [ + const data = utils.getConnectorDetails("common")["priorityRouting"]; + const routing_data = [ { connector: "stripe", merchant_connector_id: globalState.get("stripeMcaId"), @@ -68,53 +65,45 @@ describe("Priority Based Routing Test", () => { ]; cy.addRoutingConfig( fixtures.routingConfigBody, - req_data, - res_data, + data, "priority", routing_data, globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); it("retrieve-routing-call-test", () => { - let data = utils.getConnectorDetails("common")["priorityRouting"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.retrieveRoutingConfig(req_data, res_data, globalState); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + const data = utils.getConnectorDetails("common")["priorityRouting"]; + + cy.retrieveRoutingConfig(data, globalState); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); it("activate-routing-call-test", () => { - let data = utils.getConnectorDetails("common")["priorityRouting"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.activateRoutingConfig(req_data, res_data, globalState); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + const data = utils.getConnectorDetails("common")["priorityRouting"]; + + cy.activateRoutingConfig(data, globalState); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); it("payment-routing-test", () => { - let data = + const data = utils.getConnectorDetails("stripe")["card_pm"]["No3DSAutoCapture"]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createConfirmPaymentTest( fixtures.createConfirmPaymentBody, - req_data, - res_data, + data, "no_three_ds", "automatic", globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); it("retrieve-payment-call-test", () => { - cy.retrievePaymentCallTest(globalState); + cy.retrievePaymentCallTest(globalState, null); }); }); @@ -141,11 +130,8 @@ describe("Priority Based Routing Test", () => { }); it("add-routing-config", () => { - let data = utils.getConnectorDetails("common")["priorityRouting"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - - let routing_data = [ + const data = utils.getConnectorDetails("common")["priorityRouting"]; + const routing_data = [ { connector: "adyen", merchant_connector_id: globalState.get("adyenMcaId"), @@ -157,53 +143,44 @@ describe("Priority Based Routing Test", () => { ]; cy.addRoutingConfig( fixtures.routingConfigBody, - req_data, - res_data, + data, "priority", routing_data, globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); it("retrieve-routing-call-test", () => { - let data = utils.getConnectorDetails("common")["priorityRouting"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.retrieveRoutingConfig(req_data, res_data, globalState); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + const data = utils.getConnectorDetails("common")["priorityRouting"]; + + cy.retrieveRoutingConfig(data, globalState); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); it("activate-routing-call-test", () => { - let data = utils.getConnectorDetails("common")["priorityRouting"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.activateRoutingConfig(req_data, res_data, globalState); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + const data = utils.getConnectorDetails("common")["priorityRouting"]; + + cy.activateRoutingConfig(data, globalState); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); it("payment-routing-test", () => { - let data = + const data = utils.getConnectorDetails("adyen")["card_pm"]["No3DSAutoCapture"]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createConfirmPaymentTest( fixtures.createConfirmPaymentBody, - req_data, - res_data, + data, "no_three_ds", "automatic", globalState ); - if (should_continue) - should_continue = utils.should_continue_further(res_data); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); it("retrieve-payment-call-test", () => { - cy.retrievePaymentCallTest(globalState); + cy.retrievePaymentCallTest(globalState, null); }); }); }); diff --git a/cypress-tests/cypress/e2e/RoutingTest/00001-VolumeBasedRouting.cy.js b/cypress-tests/cypress/e2e/RoutingTest/00001-VolumeBasedRouting.cy.js index 665afc97581e..7d7f75e3519b 100644 --- a/cypress-tests/cypress/e2e/RoutingTest/00001-VolumeBasedRouting.cy.js +++ b/cypress-tests/cypress/e2e/RoutingTest/00001-VolumeBasedRouting.cy.js @@ -51,11 +51,8 @@ describe("Volume Based Routing Test", () => { }); it("add-routing-config", () => { - let data = utils.getConnectorDetails("common")["volumeBasedRouting"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - - let routing_data = [ + const data = utils.getConnectorDetails("common")["volumeBasedRouting"]; + const routing_data = [ { connector: { connector: "stripe", @@ -67,8 +64,7 @@ describe("Volume Based Routing Test", () => { cy.addRoutingConfig( fixtures.routingConfigBody, - req_data, - res_data, + data, "volume_split", routing_data, globalState @@ -76,28 +72,24 @@ describe("Volume Based Routing Test", () => { }); it("retrieve-routing-call-test", () => { - let data = utils.getConnectorDetails("common")["volumeBasedRouting"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.retrieveRoutingConfig(req_data, res_data, globalState); + const data = utils.getConnectorDetails("common")["volumeBasedRouting"]; + + cy.retrieveRoutingConfig(data, globalState); }); it("activate-routing-call-test", () => { - let data = utils.getConnectorDetails("common")["volumeBasedRouting"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.activateRoutingConfig(req_data, res_data, globalState); + const data = utils.getConnectorDetails("common")["volumeBasedRouting"]; + + cy.activateRoutingConfig(data, globalState); }); it("payment-routing-test", () => { - let data = + const data = utils.getConnectorDetails("stripe")["card_pm"]["No3DSAutoCapture"]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createConfirmPaymentTest( fixtures.createConfirmPaymentBody, - req_data, - res_data, + data, "no_three_ds", "automatic", globalState @@ -105,20 +97,18 @@ describe("Volume Based Routing Test", () => { }); it("retrieve-payment-call-test", () => { - cy.retrievePaymentCallTest(globalState); + cy.retrievePaymentCallTest(globalState, null); }); it("create-payment-call-test-for-eps", () => { - let data = + const data = utils.getConnectorDetails("stripe")["bank_redirect_pm"][ "PaymentIntent" ]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createPaymentIntentTest( fixtures.createPaymentBody, - req_data, - res_data, + data, "three_ds", "automatic", globalState @@ -130,13 +120,12 @@ describe("Volume Based Routing Test", () => { }); it("Confirm bank redirect", () => { - let data = utils.getConnectorDetails("stripe")["bank_redirect_pm"]["eps"]; - let req_data = data["Request"]; - let res_data = data["Response"]; + const data = + utils.getConnectorDetails("stripe")["bank_redirect_pm"]["eps"]; + cy.confirmBankRedirectCallTest( fixtures.confirmBody, - req_data, - res_data, + data, true, globalState ); @@ -144,8 +133,8 @@ describe("Volume Based Routing Test", () => { it("Handle bank redirect redirection", () => { // return_url is a static url (https://hyperswitch.io) taken from confirm-body fixture and is not updated - let expected_redirection = fixtures.confirmBody["return_url"]; - let payment_method_type = globalState.get("paymentMethodType"); + const expected_redirection = fixtures.confirmBody["return_url"]; + const payment_method_type = globalState.get("paymentMethodType"); cy.handleBankRedirectRedirection( globalState, payment_method_type, @@ -177,11 +166,8 @@ describe("Volume Based Routing Test", () => { }); it("add-routing-config", () => { - let data = utils.getConnectorDetails("common")["volumeBasedRouting"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - - let routing_data = [ + const data = utils.getConnectorDetails("common")["volumeBasedRouting"]; + const routing_data = [ { connector: { connector: "adyen", @@ -193,8 +179,7 @@ describe("Volume Based Routing Test", () => { cy.addRoutingConfig( fixtures.routingConfigBody, - req_data, - res_data, + data, "volume_split", routing_data, globalState @@ -202,28 +187,24 @@ describe("Volume Based Routing Test", () => { }); it("retrieve-routing-call-test", () => { - let data = utils.getConnectorDetails("common")["volumeBasedRouting"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.retrieveRoutingConfig(req_data, res_data, globalState); + const data = utils.getConnectorDetails("common")["volumeBasedRouting"]; + + cy.retrieveRoutingConfig(data, globalState); }); it("activate-routing-call-test", () => { - let data = utils.getConnectorDetails("common")["volumeBasedRouting"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.activateRoutingConfig(req_data, res_data, globalState); + const data = utils.getConnectorDetails("common")["volumeBasedRouting"]; + + cy.activateRoutingConfig(data, globalState); }); it("payment-routing-test-for-card", () => { - let data = + const data = utils.getConnectorDetails("adyen")["card_pm"]["No3DSAutoCapture"]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createConfirmPaymentTest( fixtures.createConfirmPaymentBody, - req_data, - res_data, + data, "no_three_ds", "automatic", globalState @@ -231,18 +212,16 @@ describe("Volume Based Routing Test", () => { }); it("retrieve-payment-call-test", () => { - cy.retrievePaymentCallTest(globalState); + cy.retrievePaymentCallTest(globalState, null); }); it("create-payment-call-test-for-eps", () => { - let data = + const data = utils.getConnectorDetails("adyen")["bank_redirect_pm"]["PaymentIntent"]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createPaymentIntentTest( fixtures.createPaymentBody, - req_data, - res_data, + data, "three_ds", "automatic", globalState @@ -254,13 +233,12 @@ describe("Volume Based Routing Test", () => { }); it("Confirm bank redirect", () => { - let data = utils.getConnectorDetails("adyen")["bank_redirect_pm"]["eps"]; - let req_data = data["Request"]; - let res_data = data["Response"]; + const data = + utils.getConnectorDetails("adyen")["bank_redirect_pm"]["eps"]; + cy.confirmBankRedirectCallTest( fixtures.confirmBody, - req_data, - res_data, + data, true, globalState ); @@ -268,8 +246,8 @@ describe("Volume Based Routing Test", () => { it("Handle bank redirect redirection", () => { // return_url is a static url (https://hyperswitch.io) taken from confirm-body fixture and is not updated - let expected_redirection = fixtures.confirmBody["return_url"]; - let payment_method_type = globalState.get("paymentMethodType"); + const expected_redirection = fixtures.confirmBody["return_url"]; + const payment_method_type = globalState.get("paymentMethodType"); cy.handleBankRedirectRedirection( globalState, payment_method_type, diff --git a/cypress-tests/cypress/e2e/RoutingTest/00002-RuleBasedRouting.cy.js b/cypress-tests/cypress/e2e/RoutingTest/00002-RuleBasedRouting.cy.js index c98f34a55bb5..304668752cd1 100644 --- a/cypress-tests/cypress/e2e/RoutingTest/00002-RuleBasedRouting.cy.js +++ b/cypress-tests/cypress/e2e/RoutingTest/00002-RuleBasedRouting.cy.js @@ -51,11 +51,8 @@ describe("Rule Based Routing Test", () => { }); it("add-routing-config", () => { - let data = utils.getConnectorDetails("common")["ruleBasedRouting"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - - let routing_data = { + const data = utils.getConnectorDetails("common")["ruleBasedRouting"]; + const routing_data = { defaultSelection: { type: "priority", data: [], @@ -121,8 +118,7 @@ describe("Rule Based Routing Test", () => { cy.addRoutingConfig( fixtures.routingConfigBody, - req_data, - res_data, + data, "advanced", routing_data, globalState @@ -130,28 +126,24 @@ describe("Rule Based Routing Test", () => { }); it("retrieve-routing-call-test", () => { - let data = utils.getConnectorDetails("common")["volumeBasedRouting"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.retrieveRoutingConfig(req_data, res_data, globalState); + const data = utils.getConnectorDetails("common")["volumeBasedRouting"]; + + cy.retrieveRoutingConfig(data, globalState); }); it("activate-routing-call-test", () => { - let data = utils.getConnectorDetails("common")["ruleBasedRouting"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.activateRoutingConfig(req_data, res_data, globalState); + const data = utils.getConnectorDetails("common")["ruleBasedRouting"]; + + cy.activateRoutingConfig(data, globalState); }); it("payment-routing-test for card", () => { - let data = + const data = utils.getConnectorDetails("stripe")["card_pm"]["No3DSAutoCapture"]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createConfirmPaymentTest( fixtures.createConfirmPaymentBody, - req_data, - res_data, + data, "no_three_ds", "automatic", globalState @@ -159,18 +151,16 @@ describe("Rule Based Routing Test", () => { }); it("retrieve-payment-call-test", () => { - cy.retrievePaymentCallTest(globalState); + cy.retrievePaymentCallTest(globalState, null); }); it("create-payment-routing-test for bank redirect", () => { - let data = + const data = utils.getConnectorDetails("adyen")["bank_redirect_pm"]["PaymentIntent"]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createPaymentIntentTest( fixtures.createPaymentBody, - req_data, - res_data, + data, "three_ds", "automatic", globalState @@ -178,14 +168,12 @@ describe("Rule Based Routing Test", () => { }); it("Confirm bank redirect", () => { - let data = + const data = utils.getConnectorDetails("adyen")["bank_redirect_pm"]["ideal"]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.confirmBankRedirectCallTest( fixtures.confirmBody, - req_data, - res_data, + data, true, globalState ); @@ -193,8 +181,8 @@ describe("Rule Based Routing Test", () => { it("Handle bank redirect redirection", () => { // return_url is a static url (https://hyperswitch.io) taken from confirm-body fixture and is not updated - let expected_redirection = fixtures.confirmBody["return_url"]; - let payment_method_type = globalState.get("paymentMethodType"); + const expected_redirection = fixtures.confirmBody["return_url"]; + const payment_method_type = globalState.get("paymentMethodType"); cy.handleBankRedirectRedirection( globalState, payment_method_type, @@ -227,11 +215,8 @@ describe("Rule Based Routing Test", () => { }); it("add-routing-config", () => { - let data = utils.getConnectorDetails("common")["ruleBasedRouting"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - - let routing_data = { + const data = utils.getConnectorDetails("common")["ruleBasedRouting"]; + const routing_data = { defaultSelection: { type: "priority", data: [ @@ -275,8 +260,7 @@ describe("Rule Based Routing Test", () => { cy.addRoutingConfig( fixtures.routingConfigBody, - req_data, - res_data, + data, "advanced", routing_data, globalState @@ -284,28 +268,24 @@ describe("Rule Based Routing Test", () => { }); it("retrieve-routing-call-test", () => { - let data = utils.getConnectorDetails("common")["volumeBasedRouting"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.retrieveRoutingConfig(req_data, res_data, globalState); + const data = utils.getConnectorDetails("common")["volumeBasedRouting"]; + + cy.retrieveRoutingConfig(data, globalState); }); it("activate-routing-call-test", () => { - let data = utils.getConnectorDetails("common")["ruleBasedRouting"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.activateRoutingConfig(req_data, res_data, globalState); + const data = utils.getConnectorDetails("common")["ruleBasedRouting"]; + + cy.activateRoutingConfig(data, globalState); }); it("create-payment-call-test-with-USD", () => { - let data = + const data = utils.getConnectorDetails("stripe")["card_pm"]["PaymentIntent"]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createPaymentIntentTest( fixtures.createPaymentBody, - req_data, - res_data, + data, "no_three_ds", "automatic", globalState @@ -313,31 +293,23 @@ describe("Rule Based Routing Test", () => { }); it("Confirm No 3DS", () => { - let data = + const data = utils.getConnectorDetails("stripe")["card_pm"]["No3DSAutoCapture"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.confirmCallTest( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); }); it("retrieve-payment-call-test", () => { - cy.retrievePaymentCallTest(globalState); + cy.retrievePaymentCallTest(globalState, null); }); it("create-payment-call-test-with-EUR", () => { - let data = utils.getConnectorDetails("adyen")["card_pm"]["PaymentIntent"]; - let req_data = data["Request"]; - let res_data = data["Response"]; + const data = + utils.getConnectorDetails("adyen")["card_pm"]["PaymentIntent"]; + cy.createPaymentIntentTest( fixtures.createPaymentBody, - req_data, - res_data, + data, "no_three_ds", "automatic", globalState @@ -345,21 +317,14 @@ describe("Rule Based Routing Test", () => { }); it("Confirm No 3DS", () => { - let data = + const data = utils.getConnectorDetails("adyen")["card_pm"]["No3DSAutoCapture"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.confirmCallTest( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); }); it("retrieve-payment-call-test", () => { - cy.retrievePaymentCallTest(globalState); + cy.retrievePaymentCallTest(globalState, null); }); }); @@ -389,11 +354,8 @@ describe("Rule Based Routing Test", () => { }); it("add-routing-config", () => { - let data = utils.getConnectorDetails("common")["ruleBasedRouting"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - - let routing_data = { + const data = utils.getConnectorDetails("common")["ruleBasedRouting"]; + const routing_data = { defaultSelection: { type: "priority", data: [], @@ -453,8 +415,7 @@ describe("Rule Based Routing Test", () => { cy.addRoutingConfig( fixtures.routingConfigBody, - req_data, - res_data, + data, "advanced", routing_data, globalState @@ -462,28 +423,24 @@ describe("Rule Based Routing Test", () => { }); it("retrieve-routing-call-test", () => { - let data = utils.getConnectorDetails("common")["volumeBasedRouting"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.retrieveRoutingConfig(req_data, res_data, globalState); + const data = utils.getConnectorDetails("common")["volumeBasedRouting"]; + + cy.retrieveRoutingConfig(data, globalState); }); it("activate-routing-call-test", () => { - let data = utils.getConnectorDetails("common")["ruleBasedRouting"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.activateRoutingConfig(req_data, res_data, globalState); + const data = utils.getConnectorDetails("common")["ruleBasedRouting"]; + + cy.activateRoutingConfig(data, globalState); }); it("create-payment-call-test-with-amount-10", () => { - let data = + const data = utils.getConnectorDetails("stripe")["card_pm"]["PaymentIntent"]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createPaymentIntentTest( fixtures.createPaymentBody, - req_data, - res_data, + data, "no_three_ds", "automatic", globalState @@ -491,32 +448,23 @@ describe("Rule Based Routing Test", () => { }); it("Confirm No 3DS", () => { - let data = + const data = utils.getConnectorDetails("stripe")["card_pm"]["No3DSAutoCapture"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.confirmCallTest( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); }); it("retrieve-payment-call-test", () => { - cy.retrievePaymentCallTest(globalState); + cy.retrievePaymentCallTest(globalState, null); }); it("create-payment-call-test-with-amount-9", () => { - let data = + const data = utils.getConnectorDetails("adyen")["card_pm"]["PaymentIntent"]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createPaymentIntentTest( fixtures.createPaymentBody, - req_data, - res_data, + data, "no_three_ds", "automatic", globalState @@ -524,20 +472,13 @@ describe("Rule Based Routing Test", () => { }); it("Confirm No 3DS", () => { - let data = + const data = utils.getConnectorDetails("adyen")["card_pm"]["No3DSAutoCapture"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.confirmCallTest( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); it("retrieve-payment-call-test", () => { - cy.retrievePaymentCallTest(globalState); + cy.retrievePaymentCallTest(globalState, null); }); }); } diff --git a/cypress-tests/cypress/e2e/RoutingTest/00003-Retries.cy.js b/cypress-tests/cypress/e2e/RoutingTest/00003-Retries.cy.js index 2638b18f479e..74c02d7ac9fd 100644 --- a/cypress-tests/cypress/e2e/RoutingTest/00003-Retries.cy.js +++ b/cypress-tests/cypress/e2e/RoutingTest/00003-Retries.cy.js @@ -42,7 +42,7 @@ describe("Auto Retries & Step Up 3DS", () => { context("Auto Retries", () => { context("[Config: enable] Auto retries", () => { it("Enable auto retries", () => { - cy.autoRetryConfig(fixtures.configs.gsm, globalState, "true"); + cy.updateConfig("autoRetry", globalState, "true"); }); context("Max auto retries", () => { @@ -59,11 +59,9 @@ describe("Auto Retries & Step Up 3DS", () => { }); it("Add routing config", () => { - let data = utils.getConnectorDetails("common")["priorityRouting"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - - let routing_data = [ + const data = + utils.getConnectorDetails("common")["priorityRouting"]; + const routing_data = [ { connector: "adyen", merchant_connector_id: globalState.get("adyenMcaId"), @@ -79,8 +77,7 @@ describe("Auto Retries & Step Up 3DS", () => { ]; cy.addRoutingConfig( fixtures.routingConfigBody, - req_data, - res_data, + data, "priority", routing_data, globalState @@ -88,35 +85,29 @@ describe("Auto Retries & Step Up 3DS", () => { }); it("Activate routing config", () => { - let data = utils.getConnectorDetails("common")["priorityRouting"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.activateRoutingConfig(req_data, res_data, globalState); + const data = + utils.getConnectorDetails("common")["priorityRouting"]; + + cy.activateRoutingConfig(data, globalState); }); }); context("Max auto retries = 2", () => { const max_auto_retries = 2; it("Update max auto retries", () => { - cy.setMaxAutoRetries( - fixtures.configs.max_auto_retries, - globalState, - `${max_auto_retries}` - ); + cy.updateConfig("maxRetries", globalState, `${max_auto_retries}`); }); context("Make payment", () => { it("Payment create call", () => { - let data = + const data = utils.getConnectorDetails("autoretries")["card_pm"][ "PaymentIntent" ]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createPaymentIntentTest( fixtures.createPaymentBody, - req_data, - res_data, + data, "no_three_ds", "automatic", globalState @@ -124,16 +115,14 @@ describe("Auto Retries & Step Up 3DS", () => { }); it("Payment confirm call", () => { - let data = + const data = utils.getConnectorDetails("autoretries")["card_pm"][ "BluesnapConfirm" ]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.confirmCallTest( fixtures.confirmBody, - req_data, - res_data, + data, true, globalState ); @@ -142,6 +131,7 @@ describe("Auto Retries & Step Up 3DS", () => { it("Payment retrieve call", () => { cy.retrievePaymentCallTest( globalState, + null, true, max_auto_retries + 1 ); @@ -152,25 +142,19 @@ describe("Auto Retries & Step Up 3DS", () => { context("Max auto retries = 1", () => { const max_auto_retries = 1; it("Update max auto retries", () => { - cy.setMaxAutoRetries( - fixtures.configs.max_auto_retries, - globalState, - `${max_auto_retries}` - ); + cy.updateConfig("maxRetries", globalState, `${max_auto_retries}`); }); context("Make payment", () => { it("Payment create call", () => { - let data = + const data = utils.getConnectorDetails("autoretries")["card_pm"][ "PaymentIntent" ]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createPaymentIntentTest( fixtures.createPaymentBody, - req_data, - res_data, + data, "no_three_ds", "automatic", globalState @@ -178,16 +162,14 @@ describe("Auto Retries & Step Up 3DS", () => { }); it("Payment confirm call", () => { - let data = + const data = utils.getConnectorDetails("autoretries")["card_pm"][ "StripeConfirmSuccess" ]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.confirmCallTest( fixtures.confirmBody, - req_data, - res_data, + data, true, globalState ); @@ -196,6 +178,7 @@ describe("Auto Retries & Step Up 3DS", () => { it("Payment retrieve call", () => { cy.retrievePaymentCallTest( globalState, + null, true, max_auto_retries + 1 ); @@ -205,25 +188,19 @@ describe("Auto Retries & Step Up 3DS", () => { context("Max auto retries = 0", () => { const max_auto_retries = 0; it("Update max auto retries", () => { - cy.setMaxAutoRetries( - fixtures.configs.max_auto_retries, - globalState, - `${max_auto_retries}` - ); + cy.updateConfig("maxRetries", globalState, `${max_auto_retries}`); }); context("Make payment", () => { it("Payment create call", () => { - let data = + const data = utils.getConnectorDetails("autoretries")["card_pm"][ "PaymentIntent" ]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createPaymentIntentTest( fixtures.createPaymentBody, - req_data, - res_data, + data, "no_three_ds", "automatic", globalState @@ -231,16 +208,14 @@ describe("Auto Retries & Step Up 3DS", () => { }); it("Payment confirm call", () => { - let data = + const data = utils.getConnectorDetails("autoretries")["card_pm"][ "AdyenConfirmFail" ]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.confirmCallTest( fixtures.confirmBody, - req_data, - res_data, + data, true, globalState ); @@ -249,6 +224,7 @@ describe("Auto Retries & Step Up 3DS", () => { it("Payment retrieve call", () => { cy.retrievePaymentCallTest( globalState, + null, true, max_auto_retries + 1 ); @@ -270,11 +246,9 @@ describe("Auto Retries & Step Up 3DS", () => { }); it("Add routing config", () => { - let data = utils.getConnectorDetails("common")["priorityRouting"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - - let routing_data = [ + const data = + utils.getConnectorDetails("common")["priorityRouting"]; + const routing_data = [ { connector: "stripe", merchant_connector_id: globalState.get("stripeMcaId"), @@ -290,8 +264,7 @@ describe("Auto Retries & Step Up 3DS", () => { ]; cy.addRoutingConfig( fixtures.routingConfigBody, - req_data, - res_data, + data, "priority", routing_data, globalState @@ -299,35 +272,29 @@ describe("Auto Retries & Step Up 3DS", () => { }); it("Activate routing config", () => { - let data = utils.getConnectorDetails("common")["priorityRouting"]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.activateRoutingConfig(req_data, res_data, globalState); + const data = + utils.getConnectorDetails("common")["priorityRouting"]; + + cy.activateRoutingConfig(data, globalState); }); }); context("Max auto retries = 2", () => { const max_auto_retries = 2; it("Update max auto retries", () => { - cy.setMaxAutoRetries( - fixtures.configs.max_auto_retries, - globalState, - `${max_auto_retries}` - ); + cy.updateConfig("maxRetries", globalState, `${max_auto_retries}`); }); context("Make payment", () => { it("Payment create call", () => { - let data = + const data = utils.getConnectorDetails("autoretries")["card_pm"][ "PaymentIntent" ]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createPaymentIntentTest( fixtures.createPaymentBody, - req_data, - res_data, + data, "no_three_ds", "automatic", globalState @@ -335,16 +302,14 @@ describe("Auto Retries & Step Up 3DS", () => { }); it("Payment confirm call", () => { - let data = + const data = utils.getConnectorDetails("autoretries")["card_pm"][ "BluesnapConfirm" ]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.confirmCallTest( fixtures.confirmBody, - req_data, - res_data, + data, true, globalState ); @@ -353,6 +318,7 @@ describe("Auto Retries & Step Up 3DS", () => { it("Payment retrieve call", () => { cy.retrievePaymentCallTest( globalState, + null, true, max_auto_retries + 1 ); @@ -363,25 +329,19 @@ describe("Auto Retries & Step Up 3DS", () => { context("Max auto retries = 1", () => { const max_auto_retries = 1; it("Update max auto retries", () => { - cy.setMaxAutoRetries( - fixtures.configs.max_auto_retries, - globalState, - `${max_auto_retries}` - ); + cy.updateConfig("maxRetries", globalState, `${max_auto_retries}`); }); context("Make payment", () => { it("Payment create call", () => { - let data = + const data = utils.getConnectorDetails("autoretries")["card_pm"][ "PaymentIntent" ]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createPaymentIntentTest( fixtures.createPaymentBody, - req_data, - res_data, + data, "no_three_ds", "automatic", globalState @@ -389,16 +349,14 @@ describe("Auto Retries & Step Up 3DS", () => { }); it("Payment confirm call", () => { - let data = + const data = utils.getConnectorDetails("autoretries")["card_pm"][ "AdyenConfirm" ]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.confirmCallTest( fixtures.confirmBody, - req_data, - res_data, + data, true, globalState ); @@ -407,6 +365,7 @@ describe("Auto Retries & Step Up 3DS", () => { it("Payment retrieve call", () => { cy.retrievePaymentCallTest( globalState, + null, true, max_auto_retries + 1 ); @@ -417,25 +376,19 @@ describe("Auto Retries & Step Up 3DS", () => { context("Max auto retries = 0", () => { const max_auto_retries = 0; it("Update max auto retries", () => { - cy.setMaxAutoRetries( - fixtures.configs.max_auto_retries, - globalState, - `${max_auto_retries}` - ); + cy.updateConfig("maxRetries", globalState, `${max_auto_retries}`); }); context("Make payment", () => { it("Payment create call", () => { - let data = + const data = utils.getConnectorDetails("autoretries")["card_pm"][ "PaymentIntent" ]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createPaymentIntentTest( fixtures.createPaymentBody, - req_data, - res_data, + data, "no_three_ds", "automatic", globalState @@ -443,16 +396,14 @@ describe("Auto Retries & Step Up 3DS", () => { }); it("Payment confirm call", () => { - let data = + const data = utils.getConnectorDetails("autoretries")["card_pm"][ "StripeConfirmFail" ]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.confirmCallTest( fixtures.confirmBody, - req_data, - res_data, + data, true, globalState ); @@ -461,6 +412,7 @@ describe("Auto Retries & Step Up 3DS", () => { it("Payment retrieve call", () => { cy.retrievePaymentCallTest( globalState, + null, true, max_auto_retries + 1 ); @@ -477,31 +429,25 @@ describe("Auto Retries & Step Up 3DS", () => { }); it("[Config: enable] Step up for Stripe", () => { - cy.stepUp(fixtures.configs.step_up, globalState, '["stripe"]'); + cy.updateConfig("stepUp", globalState, '["stripe"]'); }); }); context("Make Payment", () => { const max_auto_retries = 1; it("Update max auto retries", () => { - cy.setMaxAutoRetries( - fixtures.configs.max_auto_retries, - globalState, - `${max_auto_retries}` - ); + cy.updateConfig("maxRetries", globalState, `${max_auto_retries}`); }); it("Payment create call", () => { - let data = + const data = utils.getConnectorDetails("autoretries")["card_pm"][ "PaymentIntent" ]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createPaymentIntentTest( fixtures.createPaymentBody, - req_data, - res_data, + data, "no_three_ds", "automatic", globalState @@ -509,23 +455,21 @@ describe("Auto Retries & Step Up 3DS", () => { }); it("Payment confirm call", () => { - let data = + const data = utils.getConnectorDetails("autoretries")["card_pm"][ "StripeConfirm3DS" ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.confirmCallTest( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); }); it("Payment retrieve call", () => { - cy.retrievePaymentCallTest(globalState, true, max_auto_retries + 1); + cy.retrievePaymentCallTest( + globalState, + null, + true, + max_auto_retries + 1 + ); }); }); }); @@ -533,7 +477,7 @@ describe("Auto Retries & Step Up 3DS", () => { context("[Config: disable] Auto retries", () => { it("[Config: disable] Auto retries", () => { - cy.autoRetryConfig(fixtures.configs.gsm, globalState, "false"); + cy.updateConfig("autoRetry", globalState, "false"); }); it("[Config: disable] Step up GSM", () => { @@ -543,16 +487,14 @@ describe("Auto Retries & Step Up 3DS", () => { context("Make payment", () => { context("[Failed] Make payment", () => { it("Payment create call", () => { - let data = + const data = utils.getConnectorDetails("autoretries")["card_pm"][ "PaymentIntent" ]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createPaymentIntentTest( fixtures.createPaymentBody, - req_data, - res_data, + data, "no_three_ds", "automatic", globalState @@ -560,38 +502,29 @@ describe("Auto Retries & Step Up 3DS", () => { }); it("Payment confirm call", () => { - let data = + const data = utils.getConnectorDetails("autoretries")["card_pm"][ "StripeConfirmFail" ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.confirmCallTest( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); }); it("Payment retrieve call", () => { - cy.retrievePaymentCallTest(globalState, true); + cy.retrievePaymentCallTest(globalState, null, true); }); }); context("[Succeeded] Make payment", () => { it("Payment create call", () => { - let data = + const data = utils.getConnectorDetails("autoretries")["card_pm"][ "PaymentIntent" ]; - let req_data = data["Request"]; - let res_data = data["Response"]; + cy.createPaymentIntentTest( fixtures.createPaymentBody, - req_data, - res_data, + data, "no_three_ds", "automatic", globalState @@ -599,23 +532,16 @@ describe("Auto Retries & Step Up 3DS", () => { }); it("Payment confirm call", () => { - let data = + const data = utils.getConnectorDetails("autoretries")["card_pm"][ "StripeConfirmSuccess" ]; - let req_data = data["Request"]; - let res_data = data["Response"]; - cy.confirmCallTest( - fixtures.confirmBody, - req_data, - res_data, - true, - globalState - ); + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); }); it("Payment retrieve call", () => { - cy.retrievePaymentCallTest(globalState, true); + cy.retrievePaymentCallTest(globalState, null, true); }); }); }); diff --git a/cypress-tests/cypress/e2e/RoutingUtils/Commons.js b/cypress-tests/cypress/e2e/RoutingUtils/Commons.js index 7723ec7749b3..1131f5e671ee 100644 --- a/cypress-tests/cypress/e2e/RoutingUtils/Commons.js +++ b/cypress-tests/cypress/e2e/RoutingUtils/Commons.js @@ -51,11 +51,4 @@ export const connectorDetails = { body: {}, }, }, - jwt: { - Request: {}, - Response: { - status: 200, - body: {}, - }, - }, }; diff --git a/cypress-tests/cypress/e2e/RoutingUtils/Stripe.js b/cypress-tests/cypress/e2e/RoutingUtils/Stripe.js index 9a40c07028e8..21e885ac137c 100644 --- a/cypress-tests/cypress/e2e/RoutingUtils/Stripe.js +++ b/cypress-tests/cypress/e2e/RoutingUtils/Stripe.js @@ -3,7 +3,7 @@ import {} from "./Commons"; const successfulNo3DSCardDetails = { card_number: "4242424242424242", card_exp_month: "10", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "morino", card_cvc: "737", }; @@ -11,7 +11,7 @@ const successfulNo3DSCardDetails = { const successfulThreeDSTestCardDetails = { card_number: "4000002500003155", card_exp_month: "10", - card_exp_year: "25", + card_exp_year: "50", card_holder_name: "morino", card_cvc: "737", }; diff --git a/cypress-tests/cypress/e2e/RoutingUtils/Utils.js b/cypress-tests/cypress/e2e/RoutingUtils/Utils.js index ad92999c2874..a661c5d49924 100644 --- a/cypress-tests/cypress/e2e/RoutingUtils/Utils.js +++ b/cypress-tests/cypress/e2e/RoutingUtils/Utils.js @@ -11,7 +11,7 @@ const connectorDetails = { }; export const getConnectorDetails = (connectorId) => { - let x = getValueByKey(connectorDetails, connectorId); + const x = getValueByKey(connectorDetails, connectorId); return x; }; @@ -26,11 +26,13 @@ function getValueByKey(jsonObject, key) { } } -export const should_continue_further = (res_data) => { +export const should_continue_further = (data) => { + const resData = data.Response || {}; + if ( - res_data.body.error !== undefined || - res_data.body.error_code !== undefined || - res_data.body.error_message !== undefined + typeof resData.body.error !== "undefined" || + typeof resData.body.error_code !== "undefined" || + typeof resData.body.error_message !== "undefined" ) { return false; } else { diff --git a/cypress-tests/cypress/fixtures/business-profile.json b/cypress-tests/cypress/fixtures/business-profile.json new file mode 100644 index 000000000000..8d7bf637b592 --- /dev/null +++ b/cypress-tests/cypress/fixtures/business-profile.json @@ -0,0 +1,13 @@ +{ + "bpCreate": { + "profile_name": "default" + }, + + "bpUpdate": { + "is_connector_agnostic_mit_enabled": true, + "collect_shipping_details_from_wallet_connector": true, + "collect_billing_details_from_wallet_connector": true, + "always_collect_billing_details_from_wallet_connector": true, + "always_collect_shipping_details_from_wallet_connector": true + } +} diff --git a/cypress-tests/cypress/fixtures/confirm-body.json b/cypress-tests/cypress/fixtures/confirm-body.json index fa4769b627f4..d92be2d91e7c 100644 --- a/cypress-tests/cypress/fixtures/confirm-body.json +++ b/cypress-tests/cypress/fixtures/confirm-body.json @@ -29,7 +29,7 @@ "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, + "color_depth": 32, "screen_height": 1117, "screen_width": 1728, "time_zone": -330, diff --git a/cypress-tests/cypress/fixtures/create-confirm-body.json b/cypress-tests/cypress/fixtures/create-confirm-body.json index a779557ed5e7..df533116e56f 100644 --- a/cypress-tests/cypress/fixtures/create-confirm-body.json +++ b/cypress-tests/cypress/fixtures/create-confirm-body.json @@ -27,7 +27,7 @@ "card": { "card_number": "4242424242424242", "card_exp_month": "01", - "card_exp_year": "24", + "card_exp_year": "50", "card_holder_name": "joseph Doe", "card_cvc": "123" } @@ -70,7 +70,7 @@ "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, + "color_depth": 32, "screen_height": 1117, "screen_width": 1728, "time_zone": -330, diff --git a/cypress-tests/cypress/fixtures/create-connector-body.json b/cypress-tests/cypress/fixtures/create-connector-body.json index 5e0ce73aedc8..724119e0dfc0 100644 --- a/cypress-tests/cypress/fixtures/create-connector-body.json +++ b/cypress-tests/cypress/fixtures/create-connector-body.json @@ -1,7 +1,6 @@ { "connector_name": "stripe", - "business_country": "US", - "business_label": "default", + "profile_id": "{{profile_id}}", "connector_account_details": { "auth_type": "BodyKey", "api_key": "api-key", @@ -13,6 +12,7 @@ "metadata": { "city": "NY", "unit": "245", - "endpoint_prefix": "AD" + "endpoint_prefix": "AD", + "merchant_name": "Cypress Test" } } diff --git a/cypress-tests/cypress/fixtures/create-mandate-cit.json b/cypress-tests/cypress/fixtures/create-mandate-cit.json index c96284ea99ba..d23c1f805432 100644 --- a/cypress-tests/cypress/fixtures/create-mandate-cit.json +++ b/cypress-tests/cypress/fixtures/create-mandate-cit.json @@ -18,7 +18,7 @@ "card": { "card_number": "4242424242424242", "card_exp_month": "10", - "card_exp_year": "25", + "card_exp_year": "50", "card_holder_name": "joseph Doe", "card_cvc": "123" } @@ -80,7 +80,7 @@ "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, + "color_depth": 32, "screen_height": 1117, "screen_width": 1728, "time_zone": -330, diff --git a/cypress-tests/cypress/fixtures/create-mandate-mit.json b/cypress-tests/cypress/fixtures/create-mandate-mit.json index 9612eac32091..7b70279979aa 100644 --- a/cypress-tests/cypress/fixtures/create-mandate-mit.json +++ b/cypress-tests/cypress/fixtures/create-mandate-mit.json @@ -26,7 +26,7 @@ "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, + "color_depth": 32, "screen_height": 1117, "screen_width": 1728, "time_zone": -330, diff --git a/cypress-tests/cypress/fixtures/create-ntid-mit.json b/cypress-tests/cypress/fixtures/create-ntid-mit.json new file mode 100644 index 000000000000..0673220b54d4 --- /dev/null +++ b/cypress-tests/cypress/fixtures/create-ntid-mit.json @@ -0,0 +1,48 @@ +{ + "amount": 999, + "currency": "USD", + "confirm": true, + "payment_method": "card", + "return_url": "https://hyperswitch.io", + "email": "example@email.com", + "recurring_details": { + "type": "network_transaction_id_and_card_details", + "data": { + "card_number": "4242424242424242", + "card_exp_month": "11", + "card_exp_year": "2050", + "card_holder_name": "joseph Doe", + "network_transaction_id": "MCC5ZRGMI0925" + } + }, + "off_session": true, + "billing": { + "address": { + "first_name": "John", + "last_name": "Doe", + "line1": "1467", + "line2": "Harrison Street", + "line3": "Harrison Street", + "city": "San Fransico", + "state": "California", + "zip": "94122", + "country": "US" + }, + "phone": { + "number": "9123456789", + "country_code": "+91" + } + }, + "browser_info": { + "ip_address": "129.0.0.1", + "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 + } +} diff --git a/cypress-tests/cypress/fixtures/create-payment-body.json b/cypress-tests/cypress/fixtures/create-payment-body.json index c9982e4d7550..ea1d22d364e2 100644 --- a/cypress-tests/cypress/fixtures/create-payment-body.json +++ b/cypress-tests/cypress/fixtures/create-payment-body.json @@ -5,6 +5,7 @@ "description": "Joseph First Crypto", "email": "hyperswitch_sdk_demo_id@gmail.com", "setup_future_usage": null, + "profile_id": "{{profile_id}}", "connector_metadata": { "noon": { "order_category": "applepay" diff --git a/cypress-tests/cypress/fixtures/create-pm-id-mit.json b/cypress-tests/cypress/fixtures/create-pm-id-mit.json index c78cf2a74c5a..77d3c76b8b22 100644 --- a/cypress-tests/cypress/fixtures/create-pm-id-mit.json +++ b/cypress-tests/cypress/fixtures/create-pm-id-mit.json @@ -31,7 +31,7 @@ "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, + "color_depth": 32, "screen_height": 1117, "screen_width": 1728, "time_zone": -330, diff --git a/cypress-tests/cypress/fixtures/imports.js b/cypress-tests/cypress/fixtures/imports.js index d5cc66b7d29e..58768394cbc4 100644 --- a/cypress-tests/cypress/fixtures/imports.js +++ b/cypress-tests/cypress/fixtures/imports.js @@ -1,3 +1,4 @@ +import businessProfile from "./business-profile.json"; import captureBody from "./capture-flow-body.json"; import configs from "./configs.json"; import confirmBody from "./confirm-body.json"; @@ -17,14 +18,17 @@ import merchantUpdateBody from "./merchant-update-body.json"; import refundBody from "./refund-flow-body.json"; import routingConfigBody from "./routing-config-body.json"; import saveCardConfirmBody from "./save-card-confirm-body.json"; +import sessionTokenBody from "./session-token.json"; import apiKeyUpdateBody from "./update-api-key-body.json"; import updateConnectorBody from "./update-connector-body.json"; import customerUpdateBody from "./update-customer-body.json"; import voidBody from "./void-payment-body.json"; +import ntidConfirmBody from "./create-ntid-mit.json"; export { apiKeyCreateBody, apiKeyUpdateBody, + businessProfile, captureBody, citConfirmBody, configs, @@ -40,10 +44,12 @@ export { merchantCreateBody, merchantUpdateBody, mitConfirmBody, + ntidConfirmBody, pmIdConfirmBody, refundBody, routingConfigBody, saveCardConfirmBody, + sessionTokenBody, updateConnectorBody, voidBody, }; diff --git a/cypress-tests/cypress/fixtures/save-card-confirm-body.json b/cypress-tests/cypress/fixtures/save-card-confirm-body.json index 17a860fd1885..615cec8abf72 100644 --- a/cypress-tests/cypress/fixtures/save-card-confirm-body.json +++ b/cypress-tests/cypress/fixtures/save-card-confirm-body.json @@ -32,7 +32,7 @@ "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, + "color_depth": 32, "screen_height": 1117, "screen_width": 1728, "time_zone": -330, diff --git a/cypress-tests/cypress/fixtures/session-token.json b/cypress-tests/cypress/fixtures/session-token.json new file mode 100644 index 000000000000..84d5be3ff759 --- /dev/null +++ b/cypress-tests/cypress/fixtures/session-token.json @@ -0,0 +1,5 @@ +{ + "payment_id": "{{payment_id}}", + "client_secret": "{{client_secret}}", + "wallets": [] +} diff --git a/cypress-tests/cypress/support/commands.js b/cypress-tests/cypress/support/commands.js index 396be8e59f4c..6135f26934e4 100644 --- a/cypress-tests/cypress/support/commands.js +++ b/cypress-tests/cypress/support/commands.js @@ -25,7 +25,12 @@ // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) // commands.js or your custom support file -import { defaultErrorHandler, getValueByKey } from "../e2e/PaymentUtils/Utils"; +import { + defaultErrorHandler, + extractIntegerAtEnd, + getValueByKey, +} from "../e2e/PaymentUtils/Utils"; +import { execConfig, validateConfig } from "../utils/featureFlags"; import * as RequestBodyUtils from "../utils/RequestBodyUtils"; import { handleRedirection } from "./redirectionHandler"; @@ -37,6 +42,13 @@ function logRequestId(xRequestId) { } } +function validateErrorMessage(response, resData) { + if (resData.body.status !== "failed") { + expect(response.body.error_message).to.be.null; + expect(response.body.error_code).to.be.null; + } +} + Cypress.Commands.add( "merchantCreateCallTest", (merchantCreateBody, globalState) => { @@ -57,6 +69,7 @@ Cypress.Commands.add( logRequestId(response.headers["x-request-id"]); // Handle the response as needed + globalState.set("profileId", response.body.default_profile); globalState.set("publishableKey", response.body.publishable_key); globalState.set("merchantDetails", response.body.merchant_details); }); @@ -109,6 +122,29 @@ Cypress.Commands.add("merchantDeleteCall", (globalState) => { }); }); +Cypress.Commands.add("ListConnectorsFeatureMatrixCall", (globalState) => { + const baseUrl = globalState.get("baseUrl"); + const url = `${baseUrl}/feature_matrix`; + + cy.request({ + method: "GET", + url: url, + headers: { + Accept: "application/json", + }, + }).then((response) => { + logRequestId(response.headers["x-request-id"]); + + expect(response.body).to.have.property("connectors").and.not.empty; + expect(response.body.connectors).to.be.an("array").and.not.empty; + response.body.connectors.forEach((item) => { + expect(item).to.have.property("description").and.not.empty; + expect(item).to.have.property("category").and.not.empty; + expect(item).to.have.property("supported_payment_methods").and.not.empty; + }); + }); +}); + Cypress.Commands.add("merchantListCall", (globalState) => { const organization_id = globalState.get("organizationId"); @@ -163,6 +199,105 @@ Cypress.Commands.add( } ); +Cypress.Commands.add( + "createBusinessProfileTest", + (createBusinessProfile, globalState, profilePrefix = "profile") => { + const apiKey = globalState.get("adminApiKey"); + const baseUrl = globalState.get("baseUrl"); + const connectorId = globalState.get("connectorId"); + const merchantId = globalState.get("merchantId"); + const profileName = `${profilePrefix}_${RequestBodyUtils.generateRandomString(connectorId)}`; + const url = `${baseUrl}/account/${merchantId}/business_profile`; + + createBusinessProfile.profile_name = profileName; + + cy.request({ + method: "POST", + url: url, + headers: { + Accept: "application/json", + "Content-Type": "application/json", + "api-key": apiKey, + }, + body: createBusinessProfile, + failOnStatusCode: false, + }).then((response) => { + logRequestId(response.headers["x-request-id"]); + + globalState.set(`${profilePrefix}Id`, response.body.profile_id); + + if (response.status === 200) { + expect(response.body.profile_id).to.not.to.be.null; + } else { + throw new Error( + `Business Profile call failed ${response.body.error.message}` + ); + } + }); + } +); + +Cypress.Commands.add( + "UpdateBusinessProfileTest", + ( + updateBusinessProfileBody, + is_connector_agnostic_mit_enabled, + collect_billing_details_from_wallet_connector, + collect_shipping_details_from_wallet_connector, + always_collect_billing_details_from_wallet_connector, + always_collect_shipping_details_from_wallet_connector, + globalState, + profilePrefix = "profile" + ) => { + updateBusinessProfileBody.is_connector_agnostic_mit_enabled = + is_connector_agnostic_mit_enabled; + updateBusinessProfileBody.collect_shipping_details_from_wallet_connector = + collect_shipping_details_from_wallet_connector; + updateBusinessProfileBody.collect_billing_details_from_wallet_connector = + collect_billing_details_from_wallet_connector; + updateBusinessProfileBody.always_collect_billing_details_from_wallet_connector = + always_collect_billing_details_from_wallet_connector; + updateBusinessProfileBody.always_collect_shipping_details_from_wallet_connector = + always_collect_shipping_details_from_wallet_connector; + + const apiKey = globalState.get("adminApiKey"); + const merchantId = globalState.get("merchantId"); + const profileId = globalState.get(`${profilePrefix}Id`); + + cy.request({ + method: "POST", + url: `${globalState.get("baseUrl")}/account/${merchantId}/business_profile/${profileId}`, + headers: { + Accept: "application/json", + "Content-Type": "application/json", + "api-key": apiKey, + }, + body: updateBusinessProfileBody, + failOnStatusCode: false, + }).then((response) => { + logRequestId(response.headers["x-request-id"]); + if (response.status === 200) { + globalState.set( + "collectBillingDetails", + response.body.collect_billing_details_from_wallet_connector + ); + globalState.set( + "collectShippingDetails", + response.body.collect_shipping_details_from_wallet_connector + ); + globalState.set( + "alwaysCollectBillingDetails", + response.body.always_collect_billing_details_from_wallet_connector + ); + globalState.set( + "alwaysCollectShippingDetails", + response.body.always_collect_shipping_details_from_wallet_connector + ); + } + }); + } +); + Cypress.Commands.add("apiKeyCreateTest", (apiKeyCreateBody, globalState) => { cy.request({ method: "POST", @@ -288,21 +423,26 @@ Cypress.Commands.add( ( connectorType, createConnectorBody, - payment_methods_enabled, + paymentMethodsEnabled, globalState, connectorName, - connectorLabel + connectorLabel, + profilePrefix = "profile", + mcaPrefix = "merchantConnector" ) => { const merchantId = globalState.get("merchantId"); + const profileId = globalState.get(`${profilePrefix}Id`); + + createConnectorBody.profile_id = profileId; createConnectorBody.connector_type = connectorType; createConnectorBody.connector_name = connectorName; createConnectorBody.connector_label = connectorLabel; - createConnectorBody.payment_methods_enabled = payment_methods_enabled; + createConnectorBody.payment_methods_enabled = paymentMethodsEnabled; // readFile is used to read the contents of the file and it always returns a promise ([Object Object]) due to its asynchronous nature // it is best to use then() to handle the response within the same block of code cy.readFile(globalState.get("connectorAuthFilePath")).then( (jsonContent) => { - const authDetails = getValueByKey( + const { authDetails } = getValueByKey( JSON.stringify(jsonContent), connectorName ); @@ -324,7 +464,7 @@ Cypress.Commands.add( if (response.status === 200) { expect(connectorName).to.equal(response.body.connector_name); globalState.set( - "merchantConnectorId", + `${mcaPrefix}Id`, response.body.merchant_connector_id ); } else { @@ -349,20 +489,40 @@ Cypress.Commands.add( connectorType, createConnectorBody, payment_methods_enabled, - globalState + globalState, + profilePrefix = "profile", + mcaPrefix = "merchantConnector" ) => { - const merchantId = globalState.get("merchantId"); + const api_key = globalState.get("adminApiKey"); + const base_url = globalState.get("baseUrl"); + const connector_id = globalState.get("connectorId"); + const merchant_id = globalState.get("merchantId"); + const profile_id = globalState.get(`${profilePrefix}Id`); + const url = `${base_url}/account/${merchant_id}/connectors`; + createConnectorBody.connector_type = connectorType; - createConnectorBody.connector_name = globalState.get("connectorId"); + createConnectorBody.profile_id = profile_id; + createConnectorBody.connector_name = connector_id; createConnectorBody.payment_methods_enabled = payment_methods_enabled; + // readFile is used to read the contents of the file and it always returns a promise ([Object Object]) due to its asynchronous nature // it is best to use then() to handle the response within the same block of code cy.readFile(globalState.get("connectorAuthFilePath")).then( (jsonContent) => { - const authDetails = getValueByKey( + const { authDetails, stateUpdate } = getValueByKey( JSON.stringify(jsonContent), - globalState.get("connectorId") + connector_id, + extractIntegerAtEnd(profilePrefix) ); + + if (stateUpdate) { + // cy.task("setGlobalState", stateUpdate); + globalState.set( + "MULTIPLE_CONNECTORS", + stateUpdate.MULTIPLE_CONNECTORS + ); + } + createConnectorBody.connector_account_details = authDetails.connector_account_details; @@ -375,11 +535,11 @@ Cypress.Commands.add( cy.request({ method: "POST", - url: `${globalState.get("baseUrl")}/account/${merchantId}/connectors`, + url: url, headers: { - "Content-Type": "application/json", Accept: "application/json", - "api-key": globalState.get("adminApiKey"), + "Content-Type": "application/json", + "api-key": api_key, }, body: createConnectorBody, failOnStatusCode: false, @@ -391,7 +551,7 @@ Cypress.Commands.add( response.body.connector_name ); globalState.set( - "merchantConnectorId", + `${mcaPrefix}Id`, response.body.merchant_connector_id ); } else { @@ -414,15 +574,17 @@ Cypress.Commands.add( "createPayoutConnectorCallTest", (connectorType, createConnectorBody, globalState) => { const merchantId = globalState.get("merchantId"); - let connectorName = globalState.get("connectorId"); + const connectorName = globalState.get("connectorId"); createConnectorBody.connector_type = connectorType; createConnectorBody.connector_name = connectorName; createConnectorBody.connector_type = "payout_processor"; + createConnectorBody.profile_id = globalState.get("profileId"); + // readFile is used to read the contents of the file and it always returns a promise ([Object Object]) due to its asynchronous nature // it is best to use then() to handle the response within the same block of code cy.readFile(globalState.get("connectorAuthFilePath")).then( (jsonContent) => { - let authDetails = getValueByKey( + const { authDetails } = getValueByKey( JSON.stringify(jsonContent), `${connectorName}_payout` ); @@ -529,18 +691,24 @@ Cypress.Commands.add("connectorDeleteCall", (globalState) => { Cypress.Commands.add( "connectorUpdateCall", (connectorType, updateConnectorBody, globalState) => { - const merchant_id = globalState.get("merchantId"); + const api_key = globalState.get("adminApiKey"); + const base_url = globalState.get("baseUrl"); const connector_id = globalState.get("connectorId"); + const merchant_id = globalState.get("merchantId"); const merchant_connector_id = globalState.get("merchantConnectorId"); + const connectorLabel = `updated_${RequestBodyUtils.generateRandomString(connector_id)}`; + const url = `${base_url}/account/${merchant_id}/connectors/${merchant_connector_id}`; + updateConnectorBody.connector_type = connectorType; + updateConnectorBody.connector_label = connectorLabel; cy.request({ method: "POST", - url: `${globalState.get("baseUrl")}/account/${merchant_id}/connectors/${merchant_connector_id}`, + url: url, headers: { Accept: "application/json", "Content-Type": "application/json", - "api-key": globalState.get("adminApiKey"), + "api-key": api_key, "x-merchant-id": merchant_id, }, body: updateConnectorBody, @@ -552,7 +720,7 @@ Cypress.Commands.add( expect(response.body.merchant_connector_id).to.equal( merchant_connector_id ); - expect(response.body.connector_label).to.equal("updated_connector_label"); + expect(response.body.connector_label).to.equal(connectorLabel); }); } ); @@ -573,6 +741,11 @@ Cypress.Commands.add("connectorListByMid", (globalState) => { logRequestId(response.headers["x-request-id"]); expect(response.headers["content-type"]).to.include("application/json"); expect(response.body).to.be.an("array").and.not.empty; + response.body.forEach((item) => { + expect(item).to.not.have.property("metadata"); + expect(item).to.not.have.property("additional_merchant_data"); + expect(item).to.not.have.property("connector_wallets_details"); + }); }); }); @@ -588,9 +761,22 @@ Cypress.Commands.add( }, body: customerCreateBody, }).then((response) => { - logRequestId(response.headers["x-request-id"]); - expect(response.body.customer_id).to.not.be.empty; globalState.set("customerId", response.body.customer_id); + logRequestId(response.headers["x-request-id"]); + expect(response.body.customer_id, "customer_id").to.not.be.empty; + expect(customerCreateBody.email, "email").to.equal(response.body.email); + expect(customerCreateBody.name, "name").to.equal(response.body.name); + expect(customerCreateBody.phone, "phone").to.equal(response.body.phone); + expect(customerCreateBody.metadata, "metadata").to.deep.equal( + response.body.metadata + ); + expect(customerCreateBody.address, "address").to.deep.equal( + response.body.address + ); + expect( + customerCreateBody.phone_country_code, + "phone_country_code" + ).to.equal(response.body.phone_country_code); }); } ); @@ -694,7 +880,7 @@ Cypress.Commands.add("customerDeleteCall", (globalState) => { Cypress.Commands.add( "paymentMethodListTestLessThanEqualToOnePaymentMethod", - (res_data, globalState) => { + (resData, globalState) => { cy.request({ method: "GET", url: `${globalState.get("baseUrl")}/account/payment_methods?client_secret=${globalState.get("clientSecret")}`, @@ -709,20 +895,78 @@ Cypress.Commands.add( expect(response.headers["content-type"]).to.include("application/json"); if (response.status === 200) { expect(response.body).to.have.property("currency"); - if (res_data["payment_methods"].length == 1) { + if (resData["payment_methods"].length == 1) { function getPaymentMethodType(obj) { return obj["payment_methods"][0]["payment_method_types"][0][ "payment_method_type" ]; } - expect(getPaymentMethodType(res_data)).to.equal( + expect(getPaymentMethodType(resData)).to.equal( getPaymentMethodType(response.body) ); } else { expect(0).to.equal(response.body["payment_methods"].length); } } else { - defaultErrorHandler(response, res_data); + defaultErrorHandler(response, resData); + } + }); + } +); + +Cypress.Commands.add( + "paymentMethodListTestWithRequiredFields", + (data, globalState) => { + const apiKey = globalState.get("publishableKey"); + const baseUrl = globalState.get("baseUrl"); + const clientSecret = globalState.get("clientSecret"); + const url = `${baseUrl}/account/payment_methods?client_secret=${clientSecret}`; + + cy.request({ + method: "GET", + url: url, + headers: { + "Content-Type": "application/json", + Accept: "application/json", + "api-key": apiKey, + }, + failOnStatusCode: false, + }).then((response) => { + logRequestId(response.headers["x-request-id"]); + expect(response.headers["content-type"]).to.include("application/json"); + + if (response.status === 200) { + const responsePaymentMethods = response.body["payment_methods"]; + const responseRequiredFields = + responsePaymentMethods[0]["payment_method_types"][0][ + "required_fields" + ]; + + const expectedRequiredFields = + data["payment_methods"][0]["payment_method_types"][0][ + "required_fields" + ]; + + Object.keys(expectedRequiredFields).forEach((key) => { + const expectedField = expectedRequiredFields[key]; + const responseField = responseRequiredFields[key]; + + expect(responseField).to.exist; + expect(responseField.required_field).to.equal( + expectedField.required_field + ); + expect(responseField.display_name).to.equal( + expectedField.display_name + ); + expect(responseField.field_type).to.deep.equal( + expectedField.field_type + ); + expect(responseField.value).to.equal(expectedField.value); + }); + } else { + throw new Error( + `List payment methods failed with status code "${response.status}" and error message "${response.body.error.message}"` + ); } }); } @@ -730,7 +974,7 @@ Cypress.Commands.add( Cypress.Commands.add( "paymentMethodListTestTwoConnectorsForOnePaymentMethodCredit", - (res_data, globalState) => { + (resData, globalState) => { cy.request({ method: "GET", url: `${globalState.get("baseUrl")}/account/payment_methods?client_secret=${globalState.get("clientSecret")}`, @@ -745,7 +989,7 @@ Cypress.Commands.add( expect(response.headers["content-type"]).to.include("application/json"); if (response.status === 200) { expect(response.body).to.have.property("currency"); - if (res_data["payment_methods"].length > 0) { + if (resData["payment_methods"].length > 0) { function getPaymentMethodType(obj) { return obj["payment_methods"][0]["payment_method_types"][0][ "card_networks" @@ -753,8 +997,8 @@ Cypress.Commands.add( .slice() .sort(); } - let config_payment_method_type = getPaymentMethodType(res_data); - let response_payment_method_type = getPaymentMethodType( + const config_payment_method_type = getPaymentMethodType(resData); + const response_payment_method_type = getPaymentMethodType( response.body ); for (let i = 0; i < response_payment_method_type.length; i++) { @@ -766,40 +1010,66 @@ Cypress.Commands.add( expect(0).to.equal(response.body["payment_methods"].length); } } else { - defaultErrorHandler(response, res_data); + defaultErrorHandler(response, resData); } }); } ); +Cypress.Commands.add("sessionTokenCall", (globalState, sessionTokenBody) => { + cy.request({ + method: "POST", + url: `${globalState.get("baseUrl")}/payments/session_tokens`, + headers: { + Accept: "application/json", + "Content-Type": "application/json", + "api-key": globalState.get("publishableKey"), + }, + body: sessionTokenBody, + failOnStatusCode: false, + }).then((response) => { + logRequestId(response.headers["x-request-id"]); + }); +}); + Cypress.Commands.add( "createPaymentIntentTest", ( createPaymentBody, - req_data, - res_data, + data, authentication_type, capture_method, globalState ) => { + const { + Configs: configs = {}, + Request: reqData, + Response: resData, + } = data || {}; + if ( !createPaymentBody || typeof createPaymentBody !== "object" || - !req_data.currency + !reqData.currency ) { throw new Error( "Invalid parameters provided to createPaymentIntentTest command" ); } - for (const key in req_data) { - createPaymentBody[key] = req_data[key]; + const configInfo = execConfig(validateConfig(configs)); + const profile_id = globalState.get(`${configInfo.profilePrefix}Id`); + + for (const key in reqData) { + createPaymentBody[key] = reqData[key]; } createPaymentBody.authentication_type = authentication_type; - createPaymentBody.capture_method = capture_method; createPaymentBody.customer_id = globalState.get("customerId"); + createPaymentBody.profile_id = profile_id; + globalState.set("paymentAmount", createPaymentBody.amount); + cy.request({ method: "POST", url: `${globalState.get("baseUrl")}/payments`, @@ -815,22 +1085,88 @@ Cypress.Commands.add( expect(response.headers["content-type"]).to.include("application/json"); - if (res_data.status === 200) { + if (resData.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]); + for (const key in resData.body) { + expect(resData.body[key]).to.equal( + response.body[key], + `Expected ${resData.body[key]} but got ${response.body[key]}` + ); + } + expect(response.body.payment_id, "payment_id").to.not.be.null; + expect(response.body.merchant_id, "merchant_id").to.not.be.null; + expect(createPaymentBody.amount, "amount").to.equal( + response.body.amount + ); + expect(createPaymentBody.currency, "currency").to.equal( + response.body.currency + ); + expect(createPaymentBody.capture_method, "capture_method").to.equal( + response.body.capture_method + ); + expect( + createPaymentBody.authentication_type, + "authentication_type" + ).to.equal(response.body.authentication_type); + expect(createPaymentBody.description, "description").to.equal( + response.body.description + ); + expect(createPaymentBody.email, "email").to.equal(response.body.email); + expect(createPaymentBody.email, "customer.email").to.equal( + response.body.customer.email + ); + expect(createPaymentBody.customer_id, "customer.id").to.equal( + response.body.customer.id + ); + expect(createPaymentBody.metadata, "metadata").to.deep.equal( + response.body.metadata + ); + expect( + createPaymentBody.setup_future_usage, + "setup_future_usage" + ).to.equal(response.body.setup_future_usage); + // If 'shipping_cost' is not included in the request, the 'amount' in 'createPaymentBody' should match the 'amount_capturable' in the response. + if (typeof createPaymentBody?.shipping_cost === "undefined") { + expect(createPaymentBody.amount, "amount_capturable").to.equal( + response.body.amount_capturable + ); + } else { + expect( + createPaymentBody.amount + createPaymentBody.shipping_cost, + "amount_capturable" + ).to.equal(response.body.amount_capturable); } - expect(createPaymentBody.amount).to.equal(response.body.amount); - expect(null).to.equal(response.body.amount_received); - expect(createPaymentBody.amount).to.equal( - response.body.amount_capturable + expect(response.body.amount_received, "amount_received").to.be.oneOf([ + 0, + null, + ]); + expect(response.body.connector, "connector").to.be.null; + expect(createPaymentBody.capture_method, "capture_method").to.equal( + response.body.capture_method ); + expect(response.body.payment_method, "payment_method").to.be.null; + expect(response.body.payment_method_data, "payment_method_data").to.be + .null; + expect(response.body.merchant_connector_id, "merchant_connector_id").to + .be.null; + expect(response.body.payment_method_id, "payment_method_id").to.be.null; + expect(response.body.payment_method_id, "payment_method_status").to.be + .null; + expect(response.body.profile_id, "profile_id").to.not.be.null; + expect( + response.body.merchant_order_reference_id, + "merchant_order_reference_id" + ).to.be.null; + expect(response.body.connector_mandate_id, "connector_mandate_id").to.be + .null; + + validateErrorMessage(response, resData); } else { - defaultErrorHandler(response, res_data); + defaultErrorHandler(response, resData); } }); } @@ -853,55 +1189,174 @@ Cypress.Commands.add("paymentMethodsCallTest", (globalState) => { 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"); + if ( + globalState.get("collectBillingDetails") === true || + globalState.get("alwaysCollectBillingDetails") === true + ) { + expect( + response.body.collect_billing_details_from_wallets, + "collectBillingDetailsFromWallets" + ).to.be.true; + } else + expect( + response.body.collect_billing_details_from_wallets, + "collectBillingDetailsFromWallets" + ).to.be.false; + + if ( + globalState.get("collectShippingDetails") === true || + globalState.get("alwaysCollectShippingDetails") === true + ) { + expect( + response.body.collect_shipping_details_from_wallets, + "collectShippingDetailsFromWallets" + ).to.be.true; + } else + expect( + response.body.collect_shipping_details_from_wallets, + "collectShippingDetailsFromWallets" + ).to.be.false; globalState.set("paymentID", paymentIntentID); cy.log(response); }); }); -Cypress.Commands.add( - "createPaymentMethodTest", - (globalState, req_data, res_data) => { - req_data.customer_id = globalState.get("customerId"); +Cypress.Commands.add("createPaymentMethodTest", (globalState, data) => { + const { Request: reqData, Response: resData } = data || {}; - cy.request({ - method: "POST", - url: `${globalState.get("baseUrl")}/payment_methods`, - body: req_data, - headers: { - "Content-Type": "application/json", - Accept: "application/json", - "api-key": globalState.get("apiKey"), - }, - }).then((response) => { - logRequestId(response.headers["x-request-id"]); + reqData.customer_id = globalState.get("customerId"); + const merchant_id = globalState.get("merchantId"); - expect(response.headers["content-type"]).to.include("application/json"); - if (response.status === 200) { - expect(response.body).to.have.property("payment_method_id"); - expect(response.body).to.have.property("client_secret"); - globalState.set("paymentMethodId", response.body.payment_method_id); - } else { - defaultErrorHandler(response, res_data); - } - }); - } -); + cy.request({ + method: "POST", + url: `${globalState.get("baseUrl")}/payment_methods`, + headers: { + "Content-Type": "application/json", + Accept: "application/json", + "api-key": globalState.get("apiKey"), + }, + body: reqData, + failOnStatusCode: false, + }).then((response) => { + logRequestId(response.headers["x-request-id"]); + + expect(response.headers["content-type"]).to.include("application/json"); + if (response.status === 200) { + expect(response.body.client_secret, "client_secret").to.include( + "_secret_" + ).and.to.not.be.null; + expect(response.body.payment_method_id, "payment_method_id").to.not.be + .null; + expect(response.body.merchant_id, "merchant_id").to.equal(merchant_id); + expect(reqData.payment_method_type, "payment_method_type").to.equal( + response.body.payment_method_type + ); + expect(reqData.payment_method, "payment_method").to.equal( + response.body.payment_method + ); + expect(response.body.last_used_at, "last_used_at").to.not.be.null; + expect(reqData.customer_id, "customer_id").to.equal( + response.body.customer_id + ); + globalState.set("paymentMethodId", response.body.payment_method_id); + } else { + defaultErrorHandler(response, resData); + } + }); +}); + +Cypress.Commands.add("deletePaymentMethodTest", (globalState) => { + const apiKey = globalState.get("apiKey"); + const baseUrl = globalState.get("baseUrl"); + const paymentMethodId = globalState.get("paymentMethodId"); + const url = `${baseUrl}/payment_methods/${paymentMethodId}`; + + cy.request({ + method: "DELETE", + url: url, + headers: { + Accept: "application/json", + "api-key": apiKey, + }, + failOnStatusCode: false, + }).then((response) => { + logRequestId(response.headers["x-request-id"]); + expect(response.headers["content-type"]).to.include("application/json"); + + if (response.status === 200) { + expect(response.body.payment_method_id).to.equal(paymentMethodId); + expect(response.body.deleted).to.be.true; + } else if (response.status === 500 && baseUrl.includes("localhost")) { + // delete payment method api endpoint requires tartarus (hyperswitch card vault) to be set up since it makes a call to the locker service to delete the payment method + expect(response.body.error.code).to.include("HE_00"); + expect(response.body.error.message).to.include("Something went wrong"); + } else { + throw new Error( + `Payment Method Delete Call Failed with error message: ${response.body.error.message}` + ); + } + }); +}); + +Cypress.Commands.add("setDefaultPaymentMethodTest", (globalState) => { + const payment_method_id = globalState.get("paymentMethodId"); + const customer_id = globalState.get("customerId"); + + cy.request({ + method: "POST", + url: `${globalState.get("baseUrl")}/customers/${customer_id}/payment_methods/${payment_method_id}/default`, + headers: { + "api-key": globalState.get("apiKey"), + }, + failOnStatusCode: false, + }).then((response) => { + logRequestId(response.headers["x-request-id"]); + expect(response.headers["content-type"]).to.include("application/json"); + if (response.status === 200) { + expect(response.body).to.have.property( + "default_payment_method_id", + payment_method_id + ); + expect(response.body).to.have.property("customer_id", customer_id); + } else { + defaultErrorHandler(response); + } + }); +}); Cypress.Commands.add( "confirmCallTest", - (confirmBody, req_data, res_data, confirm, globalState) => { + (confirmBody, data, confirm, globalState) => { + const { + Configs: configs = {}, + Request: reqData, + Response: resData, + } = data || {}; + + const apiKey = globalState.get("publishableKey"); + const baseUrl = globalState.get("baseUrl"); + const configInfo = execConfig(validateConfig(configs)); + const merchantConnectorId = globalState.get( + `${configInfo.merchantConnectorPrefix}Id` + ); const paymentIntentID = globalState.get("paymentID"); - confirmBody.confirm = confirm; + const profileId = globalState.get(`${configInfo.profilePrefix}Id`); + const url = `${baseUrl}/payments/${paymentIntentID}/confirm`; + confirmBody.client_secret = globalState.get("clientSecret"); - for (const key in req_data) { - confirmBody[key] = req_data[key]; + confirmBody.confirm = confirm; + confirmBody.profile_id = profileId; + + for (const key in reqData) { + confirmBody[key] = reqData[key]; } + cy.request({ method: "POST", - url: `${globalState.get("baseUrl")}/payments/${paymentIntentID}/confirm`, + url: url, headers: { "Content-Type": "application/json", - "api-key": globalState.get("publishableKey"), + "api-key": apiKey, }, failOnStatusCode: false, body: confirmBody, @@ -911,6 +1366,24 @@ Cypress.Commands.add( if (response.status === 200) { globalState.set("paymentID", paymentIntentID); globalState.set("connectorId", response.body.connector); + expect(response.body.connector, "connector").to.equal( + globalState.get("connectorId") + ); + expect(paymentIntentID, "payment_id").to.equal( + response.body.payment_id + ); + expect(response.body.payment_method_data, "payment_method_data").to.not + .be.empty; + expect(merchantConnectorId, "connector_id").to.equal( + response.body.merchant_connector_id + ); + expect(response.body.customer, "customer").to.not.be.empty; + expect(response.body.billing, "billing_address").to.not.be.empty; + expect(response.body.profile_id, "profile_id").to.equal(profileId).and + .to.not.be.null; + + validateErrorMessage(response, resData); + if (response.body.capture_method === "automatic") { if (response.body.authentication_type === "three_ds") { expect(response.body) @@ -920,12 +1393,16 @@ Cypress.Commands.add( "nextActionUrl", response.body.next_action.redirect_to_url ); - for (const key in res_data.body) { - expect(res_data.body[key]).to.equal(response.body[key]); + for (const key in resData.body) { + expect(resData.body[key], [key]).to.deep.equal( + response.body[key] + ); } } 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 resData.body) { + expect(resData.body[key], [key]).to.deep.equal( + response.body[key] + ); } } else { throw new Error( @@ -941,9 +1418,16 @@ Cypress.Commands.add( "nextActionUrl", response.body.next_action.redirect_to_url ); + for (const key in resData.body) { + expect(resData.body[key], [key]).to.deep.equal( + response.body[key] + ); + } } 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 resData.body) { + expect(resData.body[key], [key]).to.deep.equal( + response.body[key] + ); } } else { throw new Error( @@ -956,7 +1440,7 @@ Cypress.Commands.add( ); } } else { - defaultErrorHandler(response, res_data); + defaultErrorHandler(response, resData); } }); } @@ -964,14 +1448,24 @@ Cypress.Commands.add( Cypress.Commands.add( "confirmBankRedirectCallTest", - (confirmBody, req_data, res_data, confirm, globalState) => { - const paymentIntentId = globalState.get("paymentID"); + (confirmBody, data, confirm, globalState) => { + const { + Configs: configs = {}, + Request: reqData, + Response: resData, + } = data || {}; + + const configInfo = execConfig(validateConfig(configs)); const connectorId = globalState.get("connectorId"); - for (const key in req_data) { - confirmBody[key] = req_data[key]; - } - confirmBody.confirm = confirm; + const paymentIntentId = globalState.get("paymentID"); + const profile_id = globalState.get(`${configInfo.profilePrefix}Id`); + + for (const key in reqData) { + confirmBody[key] = reqData[key]; + } confirmBody.client_secret = globalState.get("clientSecret"); + confirmBody.confirm = confirm; + confirmBody.profile_id = profile_id; cy.request({ method: "POST", @@ -990,67 +1484,71 @@ Cypress.Commands.add( globalState.set("connectorId", response.body.connector); globalState.set("paymentMethodType", confirmBody.payment_method_type); - switch (response.body.authentication_type) { - case "three_ds": - if ( - response.body.capture_method === "automatic" || - response.body.capture_method === "manual" - ) { - if (response.body.status !== "failed") { - // we get many statuses here, hence this verification - if ( - connectorId === "adyen" && - response.body.payment_method_type === "blik" - ) { - expect(response.body) - .to.have.property("next_action") - .to.have.property("type") - .to.equal("wait_screen_information"); - } else { - 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) { + validateErrorMessage(response, resData); + + switch (response.body.authentication_type) { + case "three_ds": + if ( + response.body.capture_method === "automatic" || + response.body.capture_method === "manual" + ) { + if (response.body.status !== "failed") { + // we get many statuses here, hence this verification + if ( + connectorId === "adyen" && + response.body.payment_method_type === "blik" + ) { + expect(response.body) + .to.have.property("next_action") + .to.have.property("type") + .to.equal("wait_screen_information"); + } else { + 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.status === "failed") { + expect(response.body.error_code).to.equal( + resData.body.error_code ); } - } else if (response.body.status === "failed") { - expect(response.body.error_code).to.equal( - res_data.body.error_code + } else { + throw new Error( + `Invalid capture method ${response.body.capture_method}` ); } - } else { - throw new Error( - `Invalid capture method ${response.body.capture_method}` - ); - } - break; - case "no_three_ds": - if ( - response.body.capture_method === "automatic" || - response.body.capture_method === "manual" - ) { - 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 { + break; + case "no_three_ds": + if ( + response.body.capture_method === "automatic" || + response.body.capture_method === "manual" + ) { + 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 { + throw new Error( + `Invalid capture method ${response.body.capture_method}` + ); + } + break; + default: throw new Error( - `Invalid capture method ${response.body.capture_method}` + `Invalid authentication type ${response.body.authentication_type}` ); - } - break; - default: - throw new Error( - `Invalid authentication type ${response.body.authentication_type}` - ); + } } } else { - defaultErrorHandler(response, res_data); + defaultErrorHandler(response, resData); } }); } @@ -1058,13 +1556,24 @@ Cypress.Commands.add( Cypress.Commands.add( "confirmBankTransferCallTest", - (confirmBody, req_data, res_data, confirm, globalState) => { + (confirmBody, data, confirm, globalState) => { + const { + Configs: configs = {}, + Request: reqData, + Response: resData, + } = data || {}; + + const configInfo = execConfig(validateConfig(configs)); const paymentIntentID = globalState.get("paymentID"); - for (const key in req_data) { - confirmBody[key] = req_data[key]; + const profile_id = globalState.get(`${configInfo.profilePrefix}Id`); + + for (const key in reqData) { + confirmBody[key] = reqData[key]; } - confirmBody.confirm = confirm; confirmBody.client_secret = globalState.get("clientSecret"); + confirmBody.confirm = confirm; + confirmBody.profile_id = globalState.get(profile_id); + globalState.set("paymentMethodType", confirmBody.payment_method_type); cy.request({ @@ -1081,6 +1590,9 @@ Cypress.Commands.add( expect(response.headers["content-type"]).to.include("application/json"); if (response.status === 200) { globalState.set("paymentID", paymentIntentID); + + validateErrorMessage(response, resData); + if ( response.body.capture_method === "automatic" || response.body.capture_method === "manual" @@ -1120,7 +1632,7 @@ Cypress.Commands.add( ); } } else { - defaultErrorHandler(response, res_data); + defaultErrorHandler(response, resData); } }); } @@ -1128,13 +1640,24 @@ Cypress.Commands.add( Cypress.Commands.add( "confirmUpiCall", - (confirmBody, req_data, res_data, confirm, globalState) => { + (confirmBody, data, confirm, globalState) => { + const { + Configs: configs = {}, + Request: reqData, + Response: resData, + } = data || {}; + + const configInfo = execConfig(validateConfig(configs)); const paymentId = globalState.get("paymentID"); - for (const key in req_data) { - confirmBody[key] = req_data[key]; + const profile_id = globalState.get(`${configInfo.profilePrefix}Id`); + + for (const key in reqData) { + confirmBody[key] = reqData[key]; } - confirmBody.confirm = confirm; confirmBody.client_secret = globalState.get("clientSecret"); + confirmBody.confirm = confirm; + confirmBody.profile_id = profile_id; + globalState.set("paymentMethodType", confirmBody.payment_method_type); cy.request({ @@ -1150,6 +1673,8 @@ Cypress.Commands.add( logRequestId(response.headers["x-request-id"]); expect(response.headers["content-type"]).to.include("application/json"); if (response.status === 200) { + validateErrorMessage(response, resData); + if ( response.body.capture_method === "automatic" || response.body.capture_method === "manual" @@ -1177,7 +1702,7 @@ Cypress.Commands.add( ); } } else { - defaultErrorHandler(response, res_data); + defaultErrorHandler(response, resData); } }); } @@ -1187,18 +1712,31 @@ Cypress.Commands.add( "createConfirmPaymentTest", ( createConfirmPaymentBody, - req_data, - res_data, + data, authentication_type, capture_method, globalState ) => { + const { + Configs: configs = {}, + Request: reqData, + Response: resData, + } = data || {}; + + const configInfo = execConfig(validateConfig(configs)); + const merchant_connector_id = globalState.get( + `${configInfo.merchantConnectorPrefix}Id` + ); + const profile_id = globalState.get(`${configInfo.profilePrefix}Id`); + createConfirmPaymentBody.authentication_type = authentication_type; createConfirmPaymentBody.capture_method = capture_method; createConfirmPaymentBody.customer_id = globalState.get("customerId"); - for (const key in req_data) { - createConfirmPaymentBody[key] = req_data[key]; + createConfirmPaymentBody.profile_id = profile_id; + for (const key in reqData) { + createConfirmPaymentBody[key] = reqData[key]; } + cy.request({ method: "POST", url: `${globalState.get("baseUrl")}/payments`, @@ -1210,12 +1748,33 @@ Cypress.Commands.add( body: createConfirmPaymentBody, }).then((response) => { logRequestId(response.headers["x-request-id"]); + + globalState.set("clientSecret", response.body.client_secret); + expect(response.headers["content-type"]).to.include("application/json"); + if (response.status === 200) { + globalState.set("paymentAmount", createConfirmPaymentBody.amount); + globalState.set("paymentID", response.body.payment_id); + expect(response.body.connector, "connector").to.equal( + globalState.get("connectorId") + ); + expect(response.body.payment_id, "payment_id").to.equal( + globalState.get("paymentID") + ); + expect(response.body.payment_method_data, "payment_method_data").to.not + .be.empty; + expect(response.body.merchant_connector_id, "connector_id").to.equal( + merchant_connector_id + ); + expect(response.body.customer, "customer").to.not.be.empty; + expect(response.body.billing, "billing_address").to.not.be.empty; + expect(response.body.profile_id, "profile_id").to.not.be.null; + expect(response.body).to.have.property("status"); + + validateErrorMessage(response, resData); + 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") @@ -1224,9 +1783,16 @@ Cypress.Commands.add( "nextActionUrl", response.body.next_action.redirect_to_url ); + for (const key in resData.body) { + expect(resData.body[key], [key]).to.deep.equal( + response.body[key] + ); + } } 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 resData.body) { + expect(resData.body[key], [key]).to.deep.equal( + response.body[key] + ); } } else { throw new Error( @@ -1234,9 +1800,6 @@ Cypress.Commands.add( ); } } 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") @@ -1245,9 +1808,16 @@ Cypress.Commands.add( "nextActionUrl", response.body.next_action.redirect_to_url ); + for (const key in resData.body) { + expect(resData.body[key], [key]).to.deep.equal( + response.body[key] + ); + } } 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 resData.body) { + expect(resData.body[key], [key]).to.deep.equal( + response.body[key] + ); } } else { throw new Error( @@ -1256,7 +1826,7 @@ Cypress.Commands.add( } } } else { - defaultErrorHandler(response, res_data); + defaultErrorHandler(response, resData); } }); } @@ -1265,13 +1835,30 @@ Cypress.Commands.add( // This is consequent saved card payment confirm call test(Using payment token) Cypress.Commands.add( "saveCardConfirmCallTest", - (saveCardConfirmBody, req_data, res_data, globalState) => { + (saveCardConfirmBody, data, globalState) => { + const { + Configs: configs = {}, + Request: reqData, + Response: resData, + } = data || {}; + + const configInfo = execConfig(validateConfig(configs)); + const merchant_connector_id = globalState.get( + `${configInfo.merchantConnectorPrefix}Id` + ); const paymentIntentID = globalState.get("paymentID"); - if (req_data.setup_future_usage === "on_session") { - saveCardConfirmBody.card_cvc = req_data.payment_method_data.card.card_cvc; + const profile_id = globalState.get(`${configInfo.profilePrefix}Id`); + + if (reqData.setup_future_usage === "on_session") { + saveCardConfirmBody.card_cvc = reqData.payment_method_data.card.card_cvc; } - saveCardConfirmBody.payment_token = globalState.get("paymentToken"); saveCardConfirmBody.client_secret = globalState.get("clientSecret"); + saveCardConfirmBody.payment_token = globalState.get("paymentToken"); + saveCardConfirmBody.profile_id = profile_id; + for (const key in reqData) { + saveCardConfirmBody[key] = reqData[key]; + } + cy.request({ method: "POST", url: `${globalState.get("baseUrl")}/payments/${paymentIntentID}/confirm`, @@ -1287,15 +1874,37 @@ Cypress.Commands.add( expect(response.headers["content-type"]).to.include("application/json"); if (response.status === 200) { globalState.set("paymentID", paymentIntentID); + + globalState.set("paymentID", paymentIntentID); + globalState.set("connectorId", response.body.connector); + expect(response.body.connector, "connector").to.equal( + globalState.get("connectorId") + ); + expect(paymentIntentID, "payment_id").to.equal( + response.body.payment_id + ); + expect(response.body.payment_method_data, "payment_method_data").to.not + .be.empty; + expect(merchant_connector_id, "connector_id").to.equal( + response.body.merchant_connector_id + ); + expect(response.body.customer, "customer").to.not.be.empty; + if (reqData.billing !== null) { + expect(response.body.billing, "billing_address").to.not.be.empty; + } + expect(response.body.profile_id, "profile_id").to.not.be.null; + expect(response.body.payment_token, "payment_token").to.not.be.null; + + validateErrorMessage(response, resData); + 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]); + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); } expect(response.body.customer_id).to.equal( globalState.get("customerId") @@ -1312,8 +1921,8 @@ Cypress.Commands.add( .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]); + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); } expect(response.body.customer_id).to.equal( globalState.get("customerId") @@ -1331,7 +1940,7 @@ Cypress.Commands.add( ); } } else { - defaultErrorHandler(response, res_data); + defaultErrorHandler(response, resData); } }); } @@ -1339,9 +1948,16 @@ Cypress.Commands.add( Cypress.Commands.add( "captureCallTest", - (requestBody, req_data, res_data, amount_to_capture, globalState) => { + (requestBody, data, amount_to_capture, globalState) => { + const { Configs: configs = {}, Response: resData } = data || {}; + + const configInfo = execConfig(validateConfig(configs)); const payment_id = globalState.get("paymentID"); + const profile_id = globalState.get(`${configInfo.profilePrefix}Id`); + requestBody.amount_to_capture = amount_to_capture; + requestBody.profile_id = profile_id; + cy.request({ method: "POST", url: `${globalState.get("baseUrl")}/payments/${payment_id}/capture`, @@ -1357,48 +1973,59 @@ Cypress.Commands.add( expect(response.headers["content-type"]).to.include("application/json"); 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]); + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); } } else { - defaultErrorHandler(response, res_data); + defaultErrorHandler(response, resData); } }); } ); -Cypress.Commands.add( - "voidCallTest", - (requestBody, req_data, res_data, globalState) => { - const payment_id = globalState.get("paymentID"); - cy.request({ - method: "POST", - url: `${globalState.get("baseUrl")}/payments/${payment_id}/cancel`, - headers: { - "Content-Type": "application/json", - "api-key": globalState.get("apiKey"), - }, - failOnStatusCode: false, - body: requestBody, - }).then((response) => { - logRequestId(response.headers["x-request-id"]); +Cypress.Commands.add("voidCallTest", (requestBody, data, globalState) => { + const { Configs: configs = {}, Response: resData } = data || {}; - expect(response.headers["content-type"]).to.include("application/json"); - if (response.status === 200) { - for (const key in res_data.body) { - expect(res_data.body[key]).to.equal(response.body[key]); - } - } else { - defaultErrorHandler(response, res_data); + const configInfo = execConfig(validateConfig(configs)); + const payment_id = globalState.get("paymentID"); + const profile_id = globalState.get(`${configInfo.profilePrefix}Id`); + + requestBody.profile_id = profile_id; + + cy.request({ + method: "POST", + url: `${globalState.get("baseUrl")}/payments/${payment_id}/cancel`, + headers: { + "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"); + if (response.status === 200) { + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); } - }); - } -); + } else { + defaultErrorHandler(response, resData); + } + }); +}); Cypress.Commands.add( "retrievePaymentCallTest", - (globalState, autoretries = false, attempt = 1) => { + (globalState, data, autoretries = false, attempt = 1) => { + const { Configs: configs = {} } = data || {}; + + const configInfo = execConfig(validateConfig(configs)); + const merchant_connector_id = globalState.get( + `${configInfo.merchantConnectorPrefix}Id` + ); const payment_id = globalState.get("paymentID"); + cy.request({ method: "GET", url: `${globalState.get("baseUrl")}/payments/${payment_id}?force_sync=true&expand_attempts=true`, @@ -1413,7 +2040,24 @@ Cypress.Commands.add( 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")); - globalState.set("paymentID", response.body.payment_id); + expect(response.body.profile_id, "profile_id").to.not.be.null; + expect(response.body.billing, "billing_address").to.not.be.empty; + expect(response.body.customer, "customer").to.not.be.empty; + if ( + ["succeeded", "processing", "requires_customer_action"].includes( + response.body.status + ) + ) { + expect(response.body.connector, "connector").to.equal( + globalState.get("connectorId") + ); + expect(response.body.payment_method_data, "payment_method_data").to.not + .be.empty; + expect(response.body.payment_method, "payment_method").to.not.be.null; + expect(response.body.merchant_connector_id, "connector_id").to.equal( + merchant_connector_id + ); + } if (autoretries) { expect(response.body).to.have.property("attempts"); @@ -1448,10 +2092,17 @@ Cypress.Commands.add( Cypress.Commands.add( "refundCallTest", - (requestBody, req_data, res_data, refund_amount, globalState) => { + (requestBody, data, refund_amount, globalState) => { + const { Configs: configs = {}, Response: resData } = data || {}; + const payment_id = globalState.get("paymentID"); - requestBody.payment_id = payment_id; + + // we only need this to set the delay. We don't need the return value + execConfig(validateConfig(configs)); + requestBody.amount = refund_amount; + requestBody.payment_id = payment_id; + cy.request({ method: "POST", url: `${globalState.get("baseUrl")}/refunds`, @@ -1467,61 +2118,75 @@ Cypress.Commands.add( 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]); + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); } expect(response.body.payment_id).to.equal(payment_id); } else { - defaultErrorHandler(response, res_data); + defaultErrorHandler(response, resData); } }); } ); -Cypress.Commands.add( - "syncRefundCallTest", - (req_data, res_data, globalState) => { - const refundId = globalState.get("refundId"); - cy.request({ - method: "GET", - url: `${globalState.get("baseUrl")}/refunds/${refundId}`, - headers: { - "Content-Type": "application/json", - "api-key": globalState.get("apiKey"), - }, - failOnStatusCode: false, - }).then((response) => { - logRequestId(response.headers["x-request-id"]); +Cypress.Commands.add("syncRefundCallTest", (data, globalState) => { + const { Response: resData } = data || {}; - expect(response.headers["content-type"]).to.include("application/json"); - for (const key in res_data.body) { - expect(res_data.body[key]).to.equal(response.body[key]); - } - }); - } -); + const refundId = globalState.get("refundId"); + + cy.request({ + method: "GET", + url: `${globalState.get("baseUrl")}/refunds/${refundId}`, + headers: { + "Content-Type": "application/json", + "api-key": globalState.get("apiKey"), + }, + failOnStatusCode: false, + }).then((response) => { + logRequestId(response.headers["x-request-id"]); + + expect(response.headers["content-type"]).to.include("application/json"); + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); + } + }); +}); Cypress.Commands.add( "citForMandatesCallTest", ( requestBody, - req_data, - res_data, + data, amount, confirm, capture_method, payment_type, globalState ) => { - for (const key in req_data) { - requestBody[key] = req_data[key]; + const { + Configs: configs = {}, + Request: reqData, + Response: resData, + } = data || {}; + + const configInfo = execConfig(validateConfig(configs)); + const profile_id = globalState.get(`${configInfo.profilePrefix}Id`); + const merchant_connector_id = globalState.get( + `${configInfo.merchantConnectorPrefix}Id` + ); + + for (const key in reqData) { + requestBody[key] = reqData[key]; } - requestBody.payment_type = payment_type; - requestBody.confirm = confirm; requestBody.amount = amount; requestBody.capture_method = capture_method; + requestBody.confirm = confirm; requestBody.customer_id = globalState.get("customerId"); + requestBody.payment_type = payment_type; + requestBody.profile_id = profile_id; + globalState.set("paymentAmount", requestBody.amount); + cy.request({ method: "POST", url: `${globalState.get("baseUrl")}/payments`, @@ -1537,6 +2202,21 @@ Cypress.Commands.add( if (response.status === 200) { globalState.set("paymentID", response.body.payment_id); + expect(response.body.payment_method_data, "payment_method_data").to.not + .be.empty; + expect(response.body.connector, "connector").to.equal( + globalState.get("connectorId") + ); + expect(merchant_connector_id, "connector_id").to.equal( + response.body.merchant_connector_id + ); + expect(response.body.customer, "customer").to.not.be.empty; + expect(response.body.profile_id, "profile_id").to.not.be.null; + if (response.body.status !== "failed") { + expect(response.body.payment_method_id, "payment_method_id").to.not.be + .null; + } + if (requestBody.mandate_data === null) { expect(response.body).to.have.property("payment_method_id"); globalState.set("paymentMethodId", response.body.payment_method_id); @@ -1557,12 +2237,12 @@ Cypress.Commands.add( response.body.next_action.redirect_to_url ); cy.log(nextActionUrl); - for (const key in res_data.body) { - expect(res_data.body[key]).to.equal(response.body[key]); + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); } } 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 resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); } } else { throw new Error( @@ -1580,12 +2260,12 @@ Cypress.Commands.add( response.body.next_action.redirect_to_url ); cy.log(nextActionUrl); - for (const key in res_data.body) { - expect(res_data.body[key]).to.equal(response.body[key]); + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); } } 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 resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); } } else { throw new Error( @@ -1598,7 +2278,7 @@ Cypress.Commands.add( ); } } else { - defaultErrorHandler(response, res_data); + defaultErrorHandler(response, resData); } }); } @@ -1606,12 +2286,30 @@ Cypress.Commands.add( Cypress.Commands.add( "mitForMandatesCallTest", - (requestBody, amount, confirm, capture_method, globalState) => { + (requestBody, data, amount, confirm, capture_method, globalState) => { + const { + Configs: configs = {}, + Request: reqData, + Response: resData, + } = data || {}; + const configInfo = execConfig(validateConfig(configs)); + const profile_id = globalState.get(`${configInfo.profilePrefix}Id`); + + for (const key in reqData) { + requestBody[key] = reqData[key]; + } + + const merchant_connector_id = globalState.get( + `${configInfo.merchantConnectorPrefix}Id` + ); + requestBody.amount = amount; requestBody.confirm = confirm; requestBody.capture_method = capture_method; - requestBody.mandate_id = globalState.get("mandateId"); requestBody.customer_id = globalState.get("customerId"); + requestBody.mandate_id = globalState.get("mandateId"); + requestBody.profile_id = profile_id; + globalState.set("paymentAmount", requestBody.amount); cy.request({ method: "POST", @@ -1627,6 +2325,18 @@ Cypress.Commands.add( expect(response.headers["content-type"]).to.include("application/json"); if (response.status === 200) { globalState.set("paymentID", response.body.payment_id); + expect(response.body.payment_method_data, "payment_method_data").to.not + .be.empty; + expect(response.body.connector, "connector").to.equal( + globalState.get("connectorId") + ); + expect(merchant_connector_id, "connector_id").to.equal( + response.body.merchant_connector_id + ); + expect(response.body.customer, "customer").to.not.be.empty; + expect(response.body.profile_id, "profile_id").to.not.be.null; + expect(response.body.payment_method_id, "payment_method_id").to.not.be + .null; if (response.body.capture_method === "automatic") { if (response.body.authentication_type === "three_ds") { expect(response.body) @@ -1634,8 +2344,13 @@ Cypress.Commands.add( .to.have.property("redirect_to_url"); const nextActionUrl = response.body.next_action.redirect_to_url; cy.log(nextActionUrl); + for (const key in resData.body) { + expect(resData.body[key], [key]).to.equal(response.body[key]); + } } else if (response.body.authentication_type === "no_three_ds") { - expect(response.body.status).to.equal("succeeded"); + for (const key in resData.body) { + expect(resData.body[key], [key]).to.equal(response.body[key]); + } } else { throw new Error( `Invalid authentication type ${response.body.authentication_type}` @@ -1648,8 +2363,13 @@ Cypress.Commands.add( .to.have.property("redirect_to_url"); const nextActionUrl = response.body.next_action.redirect_to_url; cy.log(nextActionUrl); + for (const key in resData.body) { + expect(resData.body[key], [key]).to.equal(response.body[key]); + } } else if (response.body.authentication_type === "no_three_ds") { - expect(response.body.status).to.equal("requires_capture"); + for (const key in resData.body) { + expect(resData.body[key], [key]).to.equal(response.body[key]); + } } else { throw new Error( `Invalid authentication type ${response.body.authentication_type}` @@ -1671,9 +2391,7 @@ Cypress.Commands.add( ); } } else { - throw new Error( - `Error Response: ${response.status}\n${response.body.error.message}\n${response.body.error.code}` - ); + defaultErrorHandler(response, resData); } }); } @@ -1681,26 +2399,61 @@ Cypress.Commands.add( Cypress.Commands.add( "mitUsingPMId", - (requestBody, amount, confirm, capture_method, globalState) => { + (requestBody, data, amount, confirm, capture_method, globalState) => { + const { + Configs: configs = {}, + Request: reqData, + Response: resData, + } = data || {}; + + const configInfo = execConfig(validateConfig(configs)); + const profileId = globalState.get(`${configInfo.profilePrefix}Id`); + + const apiKey = globalState.get("apiKey"); + const baseUrl = globalState.get("baseUrl"); + const customerId = globalState.get("customerId"); + const paymentMethodId = globalState.get("paymentMethodId"); + const url = `${baseUrl}/payments`; + + for (const key in reqData) { + requestBody[key] = reqData[key]; + } + requestBody.amount = amount; - requestBody.confirm = confirm; requestBody.capture_method = capture_method; - requestBody.recurring_details.data = globalState.get("paymentMethodId"); - requestBody.customer_id = globalState.get("customerId"); + requestBody.confirm = confirm; + requestBody.customer_id = customerId; + requestBody.profile_id = profileId; + requestBody.recurring_details.data = paymentMethodId; + cy.request({ method: "POST", - url: `${globalState.get("baseUrl")}/payments`, + url: url, headers: { "Content-Type": "application/json", - "api-key": globalState.get("apiKey"), + "api-key": apiKey, }, failOnStatusCode: false, body: requestBody, }).then((response) => { logRequestId(response.headers["x-request-id"]); expect(response.headers["content-type"]).to.include("application/json"); + if (response.status === 200) { globalState.set("paymentID", response.body.payment_id); + + expect(response.body.payment_method_id, "payment_method_id").to.include( + "pm_" + ).and.to.not.be.null; + expect( + response.body.connector_transaction_id, + "connector_transaction_id" + ).to.not.be.null; + expect( + response.body.payment_method_status, + "payment_method_status" + ).to.equal("active"); + if (response.body.capture_method === "automatic") { if (response.body.authentication_type === "three_ds") { expect(response.body) @@ -1708,8 +2461,13 @@ Cypress.Commands.add( .to.have.property("redirect_to_url"); const nextActionUrl = response.body.next_action.redirect_to_url; cy.log(nextActionUrl); + for (const key in resData.body) { + expect(resData.body[key], [key]).to.equal(response.body[key]); + } } else if (response.body.authentication_type === "no_three_ds") { - expect(response.body.status).to.equal("succeeded"); + for (const key in resData.body) { + expect(resData.body[key], [key]).to.equal(response.body[key]); + } } else { throw new Error( `Invalid authentication type ${response.body.authentication_type}` @@ -1722,8 +2480,13 @@ Cypress.Commands.add( .to.have.property("redirect_to_url"); const nextActionUrl = response.body.next_action.redirect_to_url; cy.log(nextActionUrl); + for (const key in resData.body) { + expect(resData.body[key], [key]).to.equal(response.body[key]); + } } else if (response.body.authentication_type === "no_three_ds") { - expect(response.body.status).to.equal("requires_capture"); + for (const key in resData.body) { + expect(resData.body[key], [key]).to.equal(response.body[key]); + } } else { throw new Error( `Invalid authentication type ${response.body.authentication_type}` @@ -1735,9 +2498,98 @@ Cypress.Commands.add( ); } } else { - throw new Error( - `Error Response: ${response.status}\n${response.body.error.message}\n${response.body.error.code}` - ); + defaultErrorHandler(response, resData); + } + }); + } +); + +Cypress.Commands.add( + "mitUsingNTID", + (requestBody, data, amount, confirm, capture_method, globalState) => { + const { + Configs: configs = {}, + Request: reqData, + Response: resData, + } = data || {}; + const configInfo = execConfig(validateConfig(configs)); + const profileId = globalState.get(`${configInfo.profilePrefix}Id`); + + for (const key in reqData) { + requestBody[key] = reqData[key]; + } + + requestBody.amount = amount; + requestBody.confirm = confirm; + requestBody.capture_method = capture_method; + requestBody.profile_id = profileId; + + const apiKey = globalState.get("apiKey"); + const baseUrl = globalState.get("baseUrl"); + const url = `${baseUrl}/payments`; + + cy.request({ + method: "POST", + url: url, + headers: { + "Content-Type": "application/json", + "api-key": apiKey, + }, + failOnStatusCode: false, + body: requestBody, + }).then((response) => { + logRequestId(response.headers["x-request-id"]); + + if (response.status === 200) { + expect(response.headers["content-type"]).to.include("application/json"); + + 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(nextActionUrl); + for (const key in resData.body) { + expect(resData.body[key], [key]).to.equal(response.body[key]); + } + } else if (response.body.authentication_type === "no_three_ds") { + for (const key in resData.body) { + expect(resData.body[key], [key]).to.equal(response.body[key]); + } + } else { + throw new Error( + `Invalid authentication type ${response.body.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"); + const nextActionUrl = response.body.next_action.redirect_to_url; + cy.log(nextActionUrl); + for (const key in resData.body) { + expect(resData.body[key], [key]).to.equal(response.body[key]); + } + } else if (response.body.authentication_type === "no_three_ds") { + for (const key in resData.body) { + expect(resData.body[key], [key]).to.equal(response.body[key]); + } + } else { + throw new Error( + `Invalid authentication type ${response.body.authentication_type}` + ); + } + } else { + throw new Error( + `Invalid capture method ${response.body.capture_method}` + ); + } + } else { + defaultErrorHandler(response, resData); } }); } @@ -1791,9 +2643,9 @@ Cypress.Commands.add("revokeMandateCallTest", (globalState) => { Cypress.Commands.add( "handleRedirection", (globalState, expected_redirection) => { - let connectorId = globalState.get("connectorId"); - let expected_url = new URL(expected_redirection); - let redirection_url = new URL(globalState.get("nextActionUrl")); + const connectorId = globalState.get("connectorId"); + const expected_url = new URL(expected_redirection); + const redirection_url = new URL(globalState.get("nextActionUrl")); handleRedirection( "three_ds", { redirection_url, expected_url }, @@ -1806,9 +2658,10 @@ Cypress.Commands.add( Cypress.Commands.add( "handleBankRedirectRedirection", (globalState, payment_method_type, expected_redirection) => { - let connectorId = globalState.get("connectorId"); - let expected_url = new URL(expected_redirection); - let redirection_url = new URL(globalState.get("nextActionUrl")); + const connectorId = globalState.get("connectorId"); + const expected_url = new URL(expected_redirection); + const redirection_url = new URL(globalState.get("nextActionUrl")); + // explicitly restricting `sofort` payment method by adyen from running as it stops other tests from running // trying to handle that specific case results in stripe 3ds tests to fail if (!(connectorId == "adyen" && payment_method_type == "sofort")) { @@ -1825,17 +2678,20 @@ Cypress.Commands.add( Cypress.Commands.add( "handleBankTransferRedirection", (globalState, payment_method_type, expected_redirection) => { - let connectorId = globalState.get("connectorId"); - let expected_url = new URL(expected_redirection); - let redirection_url = new URL(globalState.get("nextActionUrl")); - let next_action_type = globalState.get("nextActionType"); + const connectorId = globalState.get("connectorId"); + const expected_url = new URL(expected_redirection); + const redirection_url = new URL(globalState.get("nextActionUrl")); + const next_action_type = globalState.get("nextActionType"); + cy.log(payment_method_type); handleRedirection( "bank_transfer", { redirection_url, expected_url }, connectorId, payment_method_type, - { next_action_type } + { + next_action_type, + } ); } ); @@ -1843,9 +2699,10 @@ Cypress.Commands.add( Cypress.Commands.add( "handleUpiRedirection", (globalState, payment_method_type, expected_redirection) => { - let connectorId = globalState.get("connectorId"); - let expected_url = new URL(expected_redirection); - let redirection_url = new URL(globalState.get("nextActionUrl")); + const connectorId = globalState.get("connectorId"); + const expected_url = new URL(expected_redirection); + const redirection_url = new URL(globalState.get("nextActionUrl")); + handleRedirection( "upi", { redirection_url, expected_url }, @@ -1870,7 +2727,62 @@ Cypress.Commands.add("listCustomerPMCallTest", (globalState) => { if (response.body.customer_payment_methods[0]?.payment_token) { const paymentToken = response.body.customer_payment_methods[0].payment_token; + const paymentMethodId = + response.body.customer_payment_methods[0].payment_method_id; globalState.set("paymentToken", paymentToken); // Set paymentToken in globalState + globalState.set("paymentMethodId", paymentMethodId); // Set paymentMethodId in globalState + } else { + // We only get an empty array if something's wrong. One exception is a 4xx when no customer exist but it is handled in the test + expect(response.body) + .to.have.property("customer_payment_methods") + .to.be.an("array").and.empty; + } + expect(globalState.get("customerId"), "customer_id").to.equal( + response.body.customer_payment_methods[0].customer_id + ); + expect( + response.body.customer_payment_methods[0].payment_token, + "payment_token" + ).to.not.be.null; + expect( + response.body.customer_payment_methods[0].payment_method_id, + "payment_method_id" + ).to.not.be.null; + expect( + response.body.customer_payment_methods[0].payment_method, + "payment_method" + ).to.not.be.null; + expect( + response.body.customer_payment_methods[0].payment_method_type, + "payment_method_type" + ).to.not.be.null; + }); +}); + +Cypress.Commands.add("listCustomerPMByClientSecret", (globalState) => { + const clientSecret = globalState.get("clientSecret"); + + cy.request({ + method: "GET", + url: `${globalState.get("baseUrl")}/customers/payment_methods?client_secret=${clientSecret}`, + headers: { + "Content-Type": "application/json", + "api-key": globalState.get("publishableKey"), + }, + }).then((response) => { + logRequestId(response.headers["x-request-id"]); + expect(response.headers["content-type"]).to.include("application/json"); + if (response.body.customer_payment_methods[0]?.payment_token) { + const paymentToken = + response.body.customer_payment_methods[0].payment_token; + const paymentMethodId = + response.body.customer_payment_methods[0].payment_method_id; + globalState.set("paymentToken", paymentToken); + globalState.set("paymentMethodId", paymentMethodId); + expect( + response.body.customer_payment_methods[0].payment_method_id, + "payment_method_id" + ).to.not.be.null; } else { // We only get an empty array if something's wrong. One exception is a 4xx when no customer exist but it is handled in the test expect(response.body) @@ -1898,16 +2810,11 @@ Cypress.Commands.add("listRefundCallTest", (requestBody, globalState) => { Cypress.Commands.add( "createConfirmPayoutTest", - ( - createConfirmPayoutBody, - req_data, - res_data, - confirm, - auto_fulfill, - globalState - ) => { - for (const key in req_data) { - createConfirmPayoutBody[key] = req_data[key]; + (createConfirmPayoutBody, data, confirm, auto_fulfill, globalState) => { + const { Request: reqData, Response: resData } = data || {}; + + for (const key in reqData) { + createConfirmPayoutBody[key] = reqData[key]; } createConfirmPayoutBody.auto_fulfill = auto_fulfill; createConfirmPayoutBody.confirm = confirm; @@ -1929,11 +2836,11 @@ Cypress.Commands.add( if (response.status === 200) { globalState.set("payoutAmount", createConfirmPayoutBody.amount); globalState.set("payoutID", response.body.payout_id); - for (const key in res_data.body) { - expect(res_data.body[key]).to.equal(response.body[key]); + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); } } else { - defaultErrorHandler(response, res_data); + defaultErrorHandler(response, resData); } }); } @@ -1941,16 +2848,11 @@ Cypress.Commands.add( Cypress.Commands.add( "createConfirmWithTokenPayoutTest", - ( - createConfirmPayoutBody, - req_data, - res_data, - confirm, - auto_fulfill, - globalState - ) => { - for (const key in req_data) { - createConfirmPayoutBody[key] = req_data[key]; + (createConfirmPayoutBody, data, confirm, auto_fulfill, globalState) => { + const { Request: reqData, Response: resData } = data || {}; + + for (const key in reqData) { + createConfirmPayoutBody[key] = reqData[key]; } createConfirmPayoutBody.customer_id = globalState.get("customerId"); createConfirmPayoutBody.payout_token = globalState.get("paymentToken"); @@ -1973,11 +2875,11 @@ Cypress.Commands.add( if (response.status === 200) { globalState.set("payoutAmount", createConfirmPayoutBody.amount); globalState.set("payoutID", response.body.payout_id); - for (const key in res_data.body) { - expect(res_data.body[key]).to.equal(response.body[key]); + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); } } else { - defaultErrorHandler(response, res_data); + defaultErrorHandler(response, resData); } }); } @@ -1985,7 +2887,9 @@ Cypress.Commands.add( Cypress.Commands.add( "fulfillPayoutCallTest", - (payoutFulfillBody, req_data, res_data, globalState) => { + (payoutFulfillBody, data, globalState) => { + const { Response: resData } = data || {}; + payoutFulfillBody.payout_id = globalState.get("payoutID"); cy.request({ @@ -2002,11 +2906,11 @@ Cypress.Commands.add( expect(response.headers["content-type"]).to.include("application/json"); if (response.status === 200) { - for (const key in res_data.body) { - expect(res_data.body[key]).to.equal(response.body[key]); + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); } } else { - defaultErrorHandler(response, res_data); + defaultErrorHandler(response, resData); } }); } @@ -2014,7 +2918,9 @@ Cypress.Commands.add( Cypress.Commands.add( "updatePayoutCallTest", - (payoutConfirmBody, req_data, res_data, auto_fulfill, globalState) => { + (payoutConfirmBody, data, auto_fulfill, globalState) => { + const { Response: resData } = data || {}; + payoutConfirmBody.confirm = true; payoutConfirmBody.auto_fulfill = auto_fulfill; @@ -2032,11 +2938,11 @@ Cypress.Commands.add( expect(response.headers["content-type"]).to.include("application/json"); if (response.status === 200) { - for (const key in res_data.body) { - expect(res_data.body[key]).to.equal(response.body[key]); + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); } } else { - defaultErrorHandler(response, res_data); + defaultErrorHandler(response, resData); } }); } @@ -2188,14 +3094,16 @@ Cypress.Commands.add("ListMcaByMid", (globalState) => { Cypress.Commands.add( "addRoutingConfig", - (routingBody, req_data, res_data, type, data, globalState) => { - for (const key in req_data) { - routingBody[key] = req_data[key]; + (routingBody, data, type, routing_data, globalState) => { + const { Request: reqData, Response: resData } = data || {}; + + for (const key in reqData) { + routingBody[key] = reqData[key]; } // set profile id from env routingBody.profile_id = globalState.get("profileId"); routingBody.algorithm.type = type; - routingBody.algorithm.data = data; + routingBody.algorithm.data = routing_data; cy.request({ method: "POST", @@ -2214,128 +3122,71 @@ Cypress.Commands.add( if (response.status === 200) { expect(response.body).to.have.property("id"); globalState.set("routingConfigId", response.body.id); - for (const key in res_data.body) { - expect(res_data.body[key]).to.equal(response.body[key]); + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); } } else { - defaultErrorHandler(response, res_data); + defaultErrorHandler(response, resData); } }); } ); -Cypress.Commands.add( - "activateRoutingConfig", - (req_data, res_data, globalState) => { - let routing_config_id = globalState.get("routingConfigId"); - cy.request({ - method: "POST", - url: `${globalState.get("baseUrl")}/routing/${routing_config_id}/activate`, - headers: { - Authorization: `Bearer ${globalState.get("userInfoToken")}`, - "Content-Type": "application/json", - Cookie: `${globalState.get("cookie")}`, - }, - failOnStatusCode: false, - }).then((response) => { - logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - - if (response.status === 200) { - expect(response.body.id).to.equal(routing_config_id); - for (const key in res_data.body) { - expect(res_data.body[key]).to.equal(response.body[key]); - } - } else { - defaultErrorHandler(response, res_data); - } - }); - } -); +Cypress.Commands.add("activateRoutingConfig", (data, globalState) => { + const { Response: resData } = data || {}; -Cypress.Commands.add( - "retrieveRoutingConfig", - (req_data, res_data, globalState) => { - let routing_config_id = globalState.get("routingConfigId"); - cy.request({ - method: "GET", - url: `${globalState.get("baseUrl")}/routing/${routing_config_id}`, - headers: { - Authorization: `Bearer ${globalState.get("userInfoToken")}`, - "Content-Type": "application/json", - Cookie: `${globalState.get("cookie")}`, - }, - failOnStatusCode: false, - }).then((response) => { - logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); + const routing_config_id = globalState.get("routingConfigId"); + cy.request({ + method: "POST", + url: `${globalState.get("baseUrl")}/routing/${routing_config_id}/activate`, + headers: { + Authorization: `Bearer ${globalState.get("userInfoToken")}`, + "Content-Type": "application/json", + Cookie: `${globalState.get("cookie")}`, + }, + failOnStatusCode: false, + }).then((response) => { + logRequestId(response.headers["x-request-id"]); + expect(response.headers["content-type"]).to.include("application/json"); - if (response.status === 200) { - expect(response.body.id).to.equal(routing_config_id); - for (const key in res_data.body) { - expect(res_data.body[key]).to.equal(response.body[key]); - } - } else { - defaultErrorHandler(response, res_data); + if (response.status === 200) { + expect(response.body.id).to.equal(routing_config_id); + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); } - }); - } -); - -Cypress.Commands.add( - "autoRetryConfig", - (autoRetryGsmBody, globalState, value) => { - const key = `should_call_gsm_${globalState.get("merchantId")}`; - autoRetryGsmBody.key = key; - autoRetryGsmBody.value = value; - - cy.request({ - method: "POST", - url: `${globalState.get("baseUrl")}/configs/${key}`, - headers: { - Accept: "application/json", - "Content-Type": "application/json", - "api-key": globalState.get("adminApiKey"), - }, - body: autoRetryGsmBody, - failOnStatusCode: false, - }).then((response) => { - logRequestId(response.headers["x-request-id"]); + } else { + defaultErrorHandler(response, resData); + } + }); +}); - if (response.status === 200) { - expect(response.body).to.have.property("key").to.equal(key); - expect(response.body).to.have.property("value").to.equal(value); - } - }); - } -); +Cypress.Commands.add("retrieveRoutingConfig", (data, globalState) => { + const { Response: resData } = data || {}; -Cypress.Commands.add( - "setMaxAutoRetries", - (maxAutoRetryBody, globalState, value) => { - const key = `max_auto_retries_enabled_${globalState.get("merchantId")}`; - maxAutoRetryBody.key = key; - maxAutoRetryBody.value = value; + const routing_config_id = globalState.get("routingConfigId"); + cy.request({ + method: "GET", + url: `${globalState.get("baseUrl")}/routing/${routing_config_id}`, + headers: { + Authorization: `Bearer ${globalState.get("userInfoToken")}`, + "Content-Type": "application/json", + Cookie: `${globalState.get("cookie")}`, + }, + failOnStatusCode: false, + }).then((response) => { + logRequestId(response.headers["x-request-id"]); + expect(response.headers["content-type"]).to.include("application/json"); - cy.request({ - method: "POST", - url: `${globalState.get("baseUrl")}/configs/${key}`, - headers: { - Accept: "application/json", - "Content-Type": "application/json", - "api-key": globalState.get("adminApiKey"), - }, - body: maxAutoRetryBody, - failOnStatusCode: false, - }).then((response) => { - logRequestId(response.headers["x-request-id"]); - if (response.status === 200) { - expect(response.body).to.have.property("key").to.equal(key); - expect(response.body).to.have.property("value").to.equal(value); + if (response.status === 200) { + expect(response.body.id).to.equal(routing_config_id); + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); } - }); - } -); + } else { + defaultErrorHandler(response, resData); + } + }); +}); Cypress.Commands.add( "updateGsmConfig", @@ -2365,19 +3216,45 @@ Cypress.Commands.add( } ); -Cypress.Commands.add("stepUp", (stepUpBody, globalState, value) => { - const key = `step_up_enabled_${globalState.get("merchantId")}`; - stepUpBody.key = key; - stepUpBody.value = value; +Cypress.Commands.add("updateConfig", (configType, globalState, value) => { + const base_url = globalState.get("baseUrl"); + const merchant_id = globalState.get("merchantId"); + const api_key = globalState.get("adminApiKey"); + + let key; + let url; + let body; + + switch (configType) { + case "autoRetry": + key = `should_call_gsm_${merchant_id}`; + url = `${base_url}/configs/${key}`; + body = { key: key, value: value }; + break; + case "maxRetries": + key = `max_auto_retries_enabled_${merchant_id}`; + url = `${base_url}/configs/${key}`; + body = { key: key, value: value }; + break; + case "stepUp": + key = `step_up_enabled_${merchant_id}`; + url = `${base_url}/configs/${key}`; + body = { key: key, value: value }; + break; + default: + throw new Error( + `Invalid config type passed into the configs: "${api_key}: ${value}"` + ); + } cy.request({ method: "POST", - url: `${globalState.get("baseUrl")}/configs/${key}`, + url: url, headers: { "Content-Type": "application/json", - "api-key": globalState.get("adminApiKey"), + "api-key": api_key, }, - body: stepUpBody, + body: body, failOnStatusCode: false, }).then((response) => { logRequestId(response.headers["x-request-id"]); @@ -2388,3 +3265,69 @@ Cypress.Commands.add("stepUp", (stepUpBody, globalState, value) => { } }); }); + +Cypress.Commands.add("incrementalAuth", (globalState, data) => { + const { Request: reqData, Response: resData } = data || {}; + + const baseUrl = globalState.get("baseUrl"); + const paymentId = globalState.get("paymentID"); + const apiKey = globalState.get("apiKey"); + const url = `${baseUrl}/payments/${paymentId}/incremental_authorization`; + + cy.request({ + method: "POST", + url: url, + headers: { + "api-key": apiKey, + "Content-Type": "application/json", + }, + body: reqData, + failOnStatusCode: false, + }).then((response) => { + logRequestId(response.headers["x-request-id"]); + + if (response.status === 200) { + expect(response.body.amount_capturable, "amount_capturable").to.equal( + resData.body.amount_capturable + ); + expect(response.body.authorization_count, "authorization_count").to.be.a( + "number" + ).and.not.be.null; + expect( + response.body.incremental_authorization_allowed, + "incremental_authorization_allowed" + ).to.be.true; + expect( + response.body.incremental_authorizations, + "incremental_authorizations" + ).to.be.an("array").and.not.be.empty; + expect(response.body.payment_id, "payment_id").to.equal(paymentId); + expect(response.body.status, "status").to.equal(resData.body.status); + + for (const key in response.body.incremental_authorizations) { + expect(response.body.incremental_authorizations[key], "amount") + .to.have.property("amount") + .to.be.a("number") + .to.equal(resData.body.amount).and.not.be.null; + expect( + response.body.incremental_authorizations[key], + "error_code" + ).to.have.property("error_code").to.be.null; + expect( + response.body.incremental_authorizations[key], + "error_message" + ).to.have.property("error_message").to.be.null; + expect( + response.body.incremental_authorizations[key], + "previously_authorized_amount" + ) + .to.have.property("previously_authorized_amount") + .to.be.a("number") + .to.equal(response.body.amount).and.not.be.null; + expect(response.body.incremental_authorizations[key], "status") + .to.have.property("status") + .to.equal("success"); + } + } + }); +}); diff --git a/cypress-tests/cypress/support/redirectionHandler.js b/cypress-tests/cypress/support/redirectionHandler.js index 1a2bd0d3bd31..f6aec17c75dc 100644 --- a/cypress-tests/cypress/support/redirectionHandler.js +++ b/cypress-tests/cypress/support/redirectionHandler.js @@ -1,9 +1,10 @@ +/* eslint-disable cypress/unsafe-to-chain-command */ +/* eslint-disable cypress/no-unnecessary-waiting */ import jsQR from "jsqr"; // Define constants for wait times const TIMEOUT = 20000; // 20 seconds const WAIT_TIME = 10000; // 10 seconds -const WAIT_TIME_IATAPAY = 20000; // 20 seconds export function handleRedirection( redirection_type, @@ -156,7 +157,7 @@ function bankRedirectRedirection( cy.get("button.cookie-modal-deny-all.button-tertiary") .should("be.visible") .should("contain", "Reject All") - .click({ force: true, multiple: true }); + .click({ multiple: true }); cy.get("div#TopBanks.top-banks-multistep") .should("contain", "Demo Bank") .as("btn") @@ -225,19 +226,10 @@ function bankRedirectRedirection( case "trustpay": switch (payment_method_type) { case "eps": - cy.get("._transactionId__header__iXVd_").should( - "contain.text", - "Bank suchen ‑ mit eps zahlen." - ); - cy.get(".BankSearch_searchInput__uX_9l").type( - "Allgemeine Sparkasse Oberösterreich Bank AG{enter}" - ); - cy.get(".BankSearch_searchResultItem__lbcKm").click(); - cy.get("._transactionId__primaryButton__nCa0r").click(); - cy.get("#loginTitle").should( - "contain.text", - "eps Online-Überweisung Login" + cy.get("#bankname").type( + "Allgemeine Sparkasse Oberösterreich Bank AG (ASPKAT2LXXX / 20320)" ); + cy.get("#selectionSubmit").click(); cy.get("#user") .should("be.visible") .should("be.enabled") @@ -289,7 +281,7 @@ function threeDsRedirection(redirection_url, expected_url, connectorId) { if (connectorId === "adyen") { cy.get("iframe") .its("0.contentDocument.body") - .within((body) => { + .within(() => { cy.get('input[type="password"]').click(); cy.get('input[type="password"]').type("password"); cy.get("#buttonSubmit").click(); @@ -301,20 +293,32 @@ function threeDsRedirection(redirection_url, expected_url, connectorId) { ) { cy.get("iframe", { timeout: TIMEOUT }) .its("0.contentDocument.body") - .within((body) => { + .within(() => { cy.get('input[type="text"]').click().type("1234"); cy.get('input[value="SUBMIT"]').click(); }); + } else if (connectorId === "checkout") { + cy.get("iframe", { timeout: TIMEOUT }) + .its("0.contentDocument.body") + .within(() => { + cy.get('form[id="form"]', { timeout: WAIT_TIME }) + .should("exist") + .then(() => { + cy.get('input[id="password"]').click(); + cy.get('input[id="password"]').type("Checkout1!"); + cy.get("#txtButton").click(); + }); + }); } else if (connectorId === "nmi" || connectorId === "noon") { cy.get("iframe", { timeout: TIMEOUT }) .its("0.contentDocument.body") - .within((body) => { + .within(() => { cy.get("iframe", { timeout: TIMEOUT }) .its("0.contentDocument.body") - .within((body) => { + .within(() => { cy.get('form[name="cardholderInput"]', { timeout: TIMEOUT }) .should("exist") - .then((form) => { + .then(() => { cy.get('input[name="challengeDataEntry"]').click().type("1234"); cy.get('input[value="SUBMIT"]').click(); }); @@ -323,26 +327,50 @@ function threeDsRedirection(redirection_url, expected_url, connectorId) { } else if (connectorId === "novalnet") { cy.get("form", { timeout: WAIT_TIME }) .should("exist") - .then((form) => { + .then(() => { cy.get('input[id="submit"]').click(); }); } else if (connectorId === "stripe") { cy.get("iframe", { timeout: TIMEOUT }) .its("0.contentDocument.body") - .within((body) => { + .within(() => { cy.get("iframe") .its("0.contentDocument.body") - .within((body) => { + .within(() => { cy.get("#test-source-authorize-3ds").click(); }); }); } else if (connectorId === "trustpay") { cy.get('form[name="challengeForm"]', { timeout: WAIT_TIME }) .should("exist") - .then((form) => { + .then(() => { cy.get("#outcomeSelect").select("Approve").should("have.value", "Y"); cy.get('button[type="submit"]').click(); }); + } else if (connectorId === "worldpay") { + cy.get("iframe", { timeout: WAIT_TIME }) + .its("0.contentDocument.body") + .within(() => { + cy.get('form[name="cardholderInput"]', { timeout: WAIT_TIME }) + .should("exist") + .then(() => { + cy.get('input[name="challengeDataEntry"]').click().type("1234"); + cy.get('input[value="SUBMIT"]').click(); + }); + }); + } else if (connectorId === "fiuu") { + cy.get('form[id="cc_form"]', { timeout: TIMEOUT }) + .should("exist") + .then(() => { + cy.get('button.pay-btn[name="pay"]').click(); + cy.get("div.otp") + .invoke("text") + .then((otpText) => { + const otp = otpText.match(/\d+/)[0]; // Extract the numeric OTP + cy.get("input#otp-input").should("not.be.disabled").type(otp); + cy.get("button.pay-btn").click(); + }); + }); } else { // If connectorId is neither of adyen, trustpay, nmi, stripe, bankofamerica or cybersource, wait for 10 seconds cy.wait(WAIT_TIME); @@ -364,7 +392,7 @@ function upiRedirection( switch (payment_method_type) { case "upi_collect": cy.visit(redirection_url.href); - cy.wait(WAIT_TIME_IATAPAY).then(() => { + cy.wait(TIMEOUT).then(() => { verifyUrl = true; }); break; diff --git a/cypress-tests/cypress/utils/RequestBodyUtils.js b/cypress-tests/cypress/utils/RequestBodyUtils.js index 69edff05ca75..9b9015cad83d 100644 --- a/cypress-tests/cypress/utils/RequestBodyUtils.js +++ b/cypress-tests/cypress/utils/RequestBodyUtils.js @@ -10,7 +10,7 @@ export const setApiKey = (requestBody, apiKey) => { requestBody["connector_account_details"]["api_key"] = apiKey; }; -export const generateRandomString = (prefix = "cypress_merchant_GHAction_") => { +export const generateRandomString = (prefix = "cyMerchant") => { const uuidPart = "xxxxxxxx"; const randomString = uuidPart.replace(/[xy]/g, function (c) { @@ -19,7 +19,7 @@ export const generateRandomString = (prefix = "cypress_merchant_GHAction_") => { return v.toString(16); }); - return prefix + randomString; + return `${prefix}_${randomString}`; }; export const setMerchantId = (merchantCreateBody, merchantId) => { diff --git a/cypress-tests/cypress/utils/featureFlags.js b/cypress-tests/cypress/utils/featureFlags.js new file mode 100644 index 000000000000..6d8599820f7e --- /dev/null +++ b/cypress-tests/cypress/utils/featureFlags.js @@ -0,0 +1,189 @@ +/* eslint-disable no-console */ +const config_fields = ["CONNECTOR_CREDENTIAL", "DELAY", "TRIGGER_SKIP"]; + +const DEFAULT_CONNECTOR = "connector_1"; + +// Helper function for type and range validation +function validateType(value, type) { + if (typeof value !== type) { + console.error( + `Expected value to be of type ${type}, but got ${typeof value}.` + ); + return false; + } + return true; +} + +// Helper function to validate specific config keys based on schema rules +function validateConfigValue(key, value) { + // At present, there are only 2 api keys for connectors. Will be scaled based on the need + const SUPPORTED_CONNECTOR_CREDENTIAL = ["connector_1", "connector_2"]; + + if (config_fields.includes(key)) { + switch (key) { + case "DELAY": + if (typeof value !== "object" || value === null) { + console.error("DELAY must be an object."); + return false; + } + if (!validateType(value.STATUS, "boolean")) return false; + if ( + !value.STATUS || + typeof value.TIMEOUT !== "number" || + value.TIMEOUT < 0 || + value.TIMEOUT > 30000 + ) { + console.error( + "DELAY.TIMEOUT must be an integer between 0 and 30000 and DELAY.STATUS must be enabled." + ); + return false; + } + break; + + case "CONNECTOR_CREDENTIAL": + if (typeof value !== "object" || value === null) { + console.error("CONNECTOR_CREDENTIAL must be an object."); + return false; + } + // Validate nextConnector and multipleConnectors if present + if ( + value?.nextConnector !== undefined && + typeof value.nextConnector !== "boolean" + ) { + console.error("nextConnector must be a boolean"); + return false; + } + + if ( + value?.multipleConnectors && + typeof value.multipleConnectors.status !== "boolean" + ) { + console.error("multipleConnectors.status must be a boolean"); + return false; + } + + // Validate structure + if ( + !value.value || + !SUPPORTED_CONNECTOR_CREDENTIAL.includes(value.value) + ) { + console.error( + `Config ${key}.value must be one of ${SUPPORTED_CONNECTOR_CREDENTIAL.join(", ")}.` + ); + return false; + } + break; + + case "TRIGGER_SKIP": + case "DELAY.STATUS": + if (!validateType(value, "boolean")) return false; + break; + + default: + console.error(`Config key ${key} is invalid.`); + return false; + } + } else { + console.error(`Config key ${key} is invalid.`); + } + return true; +} + +// Function to validate the config object +export function validateConfig(configObject) { + // Configs object is an optional field in Connector Configs + // If passed, it must be a valid Object + if (typeof configObject === "undefined") { + return null; + } else if (typeof configObject !== "object" || configObject === null) { + console.error(`Provided config is invalid:\n${configObject}`); + return null; + } + + for (const key in configObject) { + if (Object.prototype.hasOwnProperty.call(configObject, key)) { + const value = configObject[key]; + if (!validateConfigValue(key, value)) { + return null; // Return null if any validation fails + } + } + } + + return configObject; +} + +export function getProfileAndConnectorId(connectorType) { + const credentials = { + connector_1: { + profileId: "profile", + connectorId: "merchantConnector", + }, + connector_2: { + profileId: "profile1", + connectorId: "merchantConnector1", + }, + }; + + return credentials[connectorType] || credentials.connector_1; +} + +function getSpecName() { + return Cypress.spec.name.toLowerCase() === "__all" + ? String( + Cypress.mocha.getRunner().suite.ctx.test.invocationDetails.relativeFile + ) + .split("/") + .pop() + .toLowerCase() + : Cypress.spec.name.toLowerCase(); +} + +function matchesSpecName(specName) { + if (!specName || !Array.isArray(specName) || specName.length === 0) { + return false; + } + + const currentSpec = getSpecName(); + return specName.some( + (name) => name && currentSpec.includes(name.toLowerCase()) + ); +} + +export function determineConnectorConfig(connectorConfig) { + // Case 1: Multiple connectors configuration + if ( + connectorConfig?.nextConnector && + connectorConfig?.multipleConnectors?.status + ) { + return "connector_2"; + } + + // Case 2: Invalid or null configuration + if (!connectorConfig || connectorConfig.value === "null") { + return DEFAULT_CONNECTOR; + } + + const { specName, value } = connectorConfig; + + // Case 3: No spec name matching needed + if (!specName) { + return value; + } + + // Case 4: Match spec name and return appropriate connector + return matchesSpecName(specName) ? value : DEFAULT_CONNECTOR; +} + +export function execConfig(configs) { + if (configs?.DELAY?.STATUS) { + cy.wait(configs.DELAY.TIMEOUT); + } + + const connectorType = determineConnectorConfig(configs?.CONNECTOR_CREDENTIAL); + const { profileId, connectorId } = getProfileAndConnectorId(connectorType); + + return { + profilePrefix: profileId, + merchantConnectorPrefix: connectorId, + }; +} diff --git a/cypress-tests/eslint.config.js b/cypress-tests/eslint.config.js new file mode 100644 index 000000000000..5e77513c97b8 --- /dev/null +++ b/cypress-tests/eslint.config.js @@ -0,0 +1,36 @@ +import pluginJs from "@eslint/js"; +import eslintConfigPrettier from "eslint-config-prettier"; +import pluginCypress from "eslint-plugin-cypress/flat"; +import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended"; +import globals from "globals"; + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + pluginJs.configs.recommended, + pluginCypress.configs.recommended, + eslintPluginPrettierRecommended, + eslintConfigPrettier, + { + languageOptions: { + globals: { + ...globals.browser, + ...globals.node, + }, + }, + rules: { + "no-unused-vars": "error", + "no-undef": "error", + "no-console": "warn", + "prefer-const": "warn", + + "cypress/assertion-before-screenshot": "warn", + "cypress/no-assigning-return-values": "warn", + "cypress/no-force": "warn", + "cypress/no-unnecessary-waiting": "warn", + "cypress/no-async-tests": "error", + "cypress/unsafe-to-chain-command": "warn", + + "prettier/prettier": "error", + }, + }, +]; diff --git a/cypress-tests/package-lock.json b/cypress-tests/package-lock.json index abac33d9e328..9fb61a5da8df 100644 --- a/cypress-tests/package-lock.json +++ b/cypress-tests/package-lock.json @@ -8,13 +8,17 @@ "name": "test", "version": "1.0.0", "license": "ISC", - "dependencies": { - "prettier": "^3.3.2" - }, "devDependencies": { - "cypress": "^13.14.1", + "@eslint/js": "^9.17.0", + "cypress": "^13.17.0", "cypress-mochawesome-reporter": "^3.8.2", - "jsqr": "^1.4.0" + "eslint": "^9.17.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-cypress": "^4.1.0", + "eslint-plugin-prettier": "^5.2.1", + "globals": "^15.14.0", + "jsqr": "^1.4.0", + "prettier": "^3.4.2" } }, "node_modules/@colors/colors": { @@ -29,9 +33,9 @@ } }, "node_modules/@cypress/request": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz", - "integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.6.tgz", + "integrity": "sha512-fi0eVdCOtKu5Ed6+E8mYxUF6ZTFJDZvHogCBelM0xVXmrDEkyM22gRArQzq1YcHPm1V47Vf/iAD+WgVdUlJCGg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -41,16 +45,16 @@ "combined-stream": "~1.0.6", "extend": "~3.0.2", "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "http-signature": "~1.3.6", + "form-data": "~4.0.0", + "http-signature": "~1.4.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", - "qs": "6.10.4", + "qs": "6.13.0", "safe-buffer": "^5.1.2", - "tough-cookie": "^4.1.3", + "tough-cookie": "^5.0.0", "tunnel-agent": "^0.6.0", "uuid": "^8.3.2" }, @@ -79,15 +83,245 @@ "ms": "^2.1.1" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.0.tgz", + "integrity": "sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.0.tgz", + "integrity": "sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", + "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz", + "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { - "version": "22.1.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.1.0.tgz", - "integrity": "sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==", + "version": "22.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", + "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "undici-types": "~6.13.0" + "undici-types": "~6.20.0" } }, "node_modules/@types/sinonjs__fake-timers": { @@ -98,9 +332,9 @@ "license": "MIT" }, "node_modules/@types/sizzle": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz", - "integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==", + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.9.tgz", + "integrity": "sha512-xzLEyKB50yqCUPUJkIsrVvoWNfFUbIZI+RspLWt8u+tIW/BetMBZtgV2LY/2o+tYH8dRvQ+eoPf3NdhQCcLE2w==", "dev": true, "license": "MIT" }, @@ -115,6 +349,29 @@ "@types/node": "*" } }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -129,6 +386,23 @@ "node": ">=8" } }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -222,8 +496,7 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, - "license": "Python-2.0", - "peer": true + "license": "Python-2.0" }, "node_modules/asn1": { "version": "0.2.6", @@ -256,9 +529,9 @@ } }, "node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "dev": true, "license": "MIT" }, @@ -290,9 +563,9 @@ } }, "node_modules/aws4": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.1.tgz", - "integrity": "sha512-u5w79Rd7SU4JaIlA/zFqG+gOiuq25q5VLyZ8E+ijJeILuTxVzZgp2CaGw/UTw6pXYN9XMO9yiqj/nEHmhTG5CA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", "dev": true, "license": "MIT" }, @@ -363,14 +636,14 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, "node_modules/braces": { @@ -460,6 +733,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -547,10 +830,24 @@ "fsevents": "~2.3.2" } }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.1.0.tgz", + "integrity": "sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==", "dev": true, "funding": [ { @@ -707,9 +1004,9 @@ "license": "MIT" }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { @@ -722,14 +1019,14 @@ } }, "node_modules/cypress": { - "version": "13.14.1", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.14.1.tgz", - "integrity": "sha512-Wo+byPmjps66hACEH5udhXINEiN3qS3jWNGRzJOjrRJF3D0+YrcP2LVB1T7oYaVQM/S+eanqEvBWYc8cf7Vcbg==", + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.17.0.tgz", + "integrity": "sha512-5xWkaPurwkIljojFidhw8lFScyxhtiFHl/i/3zov+1Z5CmY4t9tjIdvSXfu82Y3w7wt0uR9KkucbhkVvJZLQSA==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { - "@cypress/request": "^3.0.1", + "@cypress/request": "^3.0.6", "@cypress/xvfb": "^1.2.4", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", @@ -740,6 +1037,7 @@ "cachedir": "^2.3.0", "chalk": "^4.1.0", "check-more-types": "^2.24.0", + "ci-info": "^4.0.0", "cli-cursor": "^3.1.0", "cli-table3": "~0.6.1", "commander": "^6.2.1", @@ -754,7 +1052,6 @@ "figures": "^3.2.0", "fs-extra": "^9.1.0", "getos": "^3.2.1", - "is-ci": "^3.0.1", "is-installed-globally": "~0.4.0", "lazy-ass": "^1.6.0", "listr2": "^3.8.3", @@ -769,6 +1066,7 @@ "semver": "^7.5.3", "supports-color": "^8.1.1", "tmp": "~0.2.3", + "tree-kill": "1.2.2", "untildify": "^4.0.0", "yauzl": "^2.10.0" }, @@ -854,20 +1152,20 @@ } }, "node_modules/dayjs": { - "version": "1.11.12", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.12.tgz", - "integrity": "sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg==", + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", "dev": true, "license": "MIT" }, "node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -892,6 +1190,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -996,9 +1301,9 @@ } }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "license": "MIT", "engines": { @@ -1013,13 +1318,227 @@ "license": "MIT" }, "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.8.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", + "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.9.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.17.0", + "@eslint/plugin-kit": "^0.2.3", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-cypress": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-4.1.0.tgz", + "integrity": "sha512-JhqkMY02mw74USwK9OFhectx3YSj6Co1NgWBxlGdKvlqiAp9vdEuQqt33DKGQFvvGS/NWtduuhWXWNnU29xDSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "globals": "^15.11.0" + }, + "peerDependencies": { + "eslint": ">=9" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", + "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.9.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" } }, "node_modules/eventemitter2": { @@ -1104,6 +1623,34 @@ ], "license": "MIT" }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -1130,6 +1677,29 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -1150,7 +1720,6 @@ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -1173,6 +1742,27 @@ "flat": "cli.js" } }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "dev": true, + "license": "ISC" + }, "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -1184,18 +1774,18 @@ } }, "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", + "combined-stream": "^1.0.8", "mime-types": "^2.1.12" }, "engines": { - "node": ">= 0.12" + "node": ">= 6" } }, "node_modules/fs-extra": { @@ -1342,18 +1932,42 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "license": "ISC", "peer": true, "dependencies": { - "is-glob": "^4.0.1" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">= 6" + "node": ">=10" } }, "node_modules/global-dirs": { @@ -1372,6 +1986,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globals": { + "version": "15.14.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", + "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -1466,15 +2093,15 @@ } }, "node_modules/http-signature": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", - "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", + "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", "dev": true, "license": "MIT", "dependencies": { "assert-plus": "^1.0.0", "jsprim": "^2.0.2", - "sshpk": "^1.14.1" + "sshpk": "^1.18.0" }, "engines": { "node": ">=0.10" @@ -1511,6 +2138,43 @@ ], "license": "BSD-3-Clause" }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", @@ -1564,26 +2228,12 @@ "node": ">=8" } }, - "node_modules/is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ci-info": "^3.2.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -1604,7 +2254,6 @@ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -1721,7 +2370,6 @@ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "argparse": "^2.0.1" }, @@ -1736,6 +2384,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", @@ -1743,6 +2398,20 @@ "dev": true, "license": "(AFL-2.1 OR BSD-3-Clause)" }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -1786,6 +2455,16 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/lazy-ass": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", @@ -1796,6 +2475,20 @@ "node": "> 0.8" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/listr2": { "version": "3.14.0", "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", @@ -1830,7 +2523,6 @@ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -1876,6 +2568,13 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", @@ -2006,17 +2705,16 @@ } }, "node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=10" + "node": "*" } }, "node_modules/minimist": { @@ -2030,9 +2728,9 @@ } }, "node_modules/mocha": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.0.tgz", - "integrity": "sha512-v8/rBWr2VO5YkspYINnvu81inSz2y3ODJrhO175/Exzor1RcEZZkizgE2A+w/CAXXoESS8Kys5E62dOHGHzULA==", + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", "dev": true, "license": "MIT", "peer": true, @@ -2066,27 +2764,30 @@ "node": ">= 14.0.0" } }, - "node_modules/mocha/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "license": "MIT", "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "balanced-match": "^1.0.0" } }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "node_modules/mocha/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, - "license": "MIT", - "peer": true + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } }, "node_modules/mochawesome": { "version": "7.1.3", @@ -2128,17 +2829,6 @@ "node": ">=10.0.0" } }, - "node_modules/mochawesome-merge/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/mochawesome-merge/node_modules/camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -2245,19 +2935,6 @@ "node": ">=8" } }, - "node_modules/mochawesome-merge/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/mochawesome-merge/node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -2440,9 +3117,16 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true, "license": "MIT" }, @@ -2481,9 +3165,9 @@ } }, "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", "dev": true, "license": "MIT", "engines": { @@ -2529,6 +3213,24 @@ "opener": "bin/opener-bin.js" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/ospath": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", @@ -2542,7 +3244,6 @@ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -2559,7 +3260,6 @@ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -2596,6 +3296,19 @@ "node": ">=6" } }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -2664,10 +3377,21 @@ "node": ">=0.10.0" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", + "dev": true, "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -2679,6 +3403,19 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -2721,17 +3458,10 @@ "dev": true, "license": "MIT" }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true, - "license": "MIT" - }, "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", "dev": true, "license": "MIT", "dependencies": { @@ -2750,13 +3480,13 @@ } }, "node_modules/qs": { - "version": "6.10.4", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz", - "integrity": "sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -2765,13 +3495,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true, - "license": "MIT" - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -2831,12 +3554,15 @@ "dev": true, "license": "ISC" }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=4" + } }, "node_modules/restore-cursor": { "version": "3.1.0", @@ -3080,7 +3806,6 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" }, @@ -3104,6 +3829,23 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/synckit": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", + "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/tcomb": { "version": "3.2.29", "resolved": "https://registry.npmjs.org/tcomb/-/tcomb-3.2.29.tgz", @@ -3138,6 +3880,26 @@ "dev": true, "license": "MIT" }, + "node_modules/tldts": { + "version": "6.1.64", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.64.tgz", + "integrity": "sha512-ph4AE5BXWIOsSy9stpoeo7bYe/Cy7VfpciIH4RhVZUPItCJmhqWCN0EVzxd8BOHiyNb42vuJc6NWTjJkg91Tuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.64" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.64", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.64.tgz", + "integrity": "sha512-uqnl8vGV16KsyflHOzqrYjjArjfXaU6rMPXYy2/ZWoRKCkXtghgB4VwTDXUG+t0OTGeSewNAG31/x1gCTfLt+Q==", + "dev": true, + "license": "MIT" + }, "node_modules/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", @@ -3163,35 +3925,32 @@ } }, "node_modules/tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", + "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" + "tldts": "^6.1.32" }, "engines": { - "node": ">=6" + "node": ">=16" } }, - "node_modules/tough-cookie/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 4.0.0" + "bin": { + "tree-kill": "cli.js" } }, "node_modules/tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, "license": "0BSD" }, @@ -3215,6 +3974,19 @@ "dev": true, "license": "Unlicense" }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", @@ -3229,9 +4001,9 @@ } }, "node_modules/undici-types": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.13.0.tgz", - "integrity": "sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", "dev": true, "license": "MIT", "optional": true @@ -3256,15 +4028,14 @@ "node": ">=8" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" + "punycode": "^2.1.0" } }, "node_modules/uuid": { @@ -3325,6 +4096,16 @@ "dev": true, "license": "ISC" }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/workerpool": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", @@ -3433,7 +4214,6 @@ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, diff --git a/cypress-tests/package.json b/cypress-tests/package.json index 7bb40354efdd..7032a72fb084 100644 --- a/cypress-tests/package.json +++ b/cypress-tests/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "", "main": "index.js", + "type": "module", "scripts": { "cypress": "npx cypress open", "cypress-e2e": "npx cypress run --e2e", @@ -10,16 +11,23 @@ "cypress:payments": "cypress run --headless --spec 'cypress/e2e/PaymentTest/**/*'", "cypress:payouts": "cypress run --headless --spec 'cypress/e2e/PayoutTest/**/*'", "cypress:payment-method-list": "cypress run --headless --spec 'cypress/e2e/PaymentMethodListTest/**/*'", - "cypress:routing": "cypress run --headless --spec 'cypress/e2e/RoutingTest/**/*'" + "cypress:routing": "cypress run --headless --spec 'cypress/e2e/RoutingTest/**/*'", + "format": "prettier --config .prettierrc . --write", + "format:check": "prettier --config .prettierrc . --check", + "lint": "eslint ." }, - "author": "", + "author": "Hyperswitch", "license": "ISC", "devDependencies": { - "cypress": "^13.14.1", + "@eslint/js": "^9.17.0", + "cypress": "^13.17.0", "cypress-mochawesome-reporter": "^3.8.2", - "jsqr": "^1.4.0" - }, - "dependencies": { - "prettier": "^3.3.2" + "eslint": "^9.17.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-cypress": "^4.1.0", + "eslint-plugin-prettier": "^5.2.1", + "globals": "^15.14.0", + "jsqr": "^1.4.0", + "prettier": "^3.4.2" } } diff --git a/docker-compose-development.yml b/docker-compose-development.yml index cf12982a9bd3..d7a0e365c361 100644 --- a/docker-compose-development.yml +++ b/docker-compose-development.yml @@ -26,7 +26,7 @@ services: - POSTGRES_PASSWORD=db_pass - POSTGRES_DB=hyperswitch_db healthcheck: - test: ["CMD-SHELL", "pg_isready"] + test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"] interval: 5s retries: 3 start_period: 5s @@ -59,10 +59,13 @@ services: ### Application services hyperswitch-server: - image: rust:latest - command: | - apt-get install -y protobuf-compiler && \ - cargo run --bin router -- -f ./config/docker_compose.toml + build: + dockerfile_inline: | + FROM rust:latest + RUN apt-get update && \ + apt-get install -y protobuf-compiler + RUN rustup component add rustfmt clippy + command: cargo run --bin router -- -f ./config/docker_compose.toml working_dir: /app ports: - "8080:8080" diff --git a/docker-compose.yml b/docker-compose.yml index f766ff91053e..7450a6501194 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,7 +21,7 @@ services: - POSTGRES_PASSWORD=db_pass - POSTGRES_DB=hyperswitch_db healthcheck: - test: ["CMD-SHELL", "pg_isready"] + test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"] interval: 5s retries: 3 start_period: 5s diff --git a/docs/imgs/hyperswitch-logo-dark.svg b/docs/imgs/hyperswitch-logo-dark.svg index fbf0d89d4106..f07be0cea141 100644 --- a/docs/imgs/hyperswitch-logo-dark.svg +++ b/docs/imgs/hyperswitch-logo-dark.svg @@ -1,29 +1,21 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/docs/imgs/hyperswitch-logo-light.svg b/docs/imgs/hyperswitch-logo-light.svg index c951a909dd49..66b2c279d06f 100644 --- a/docs/imgs/hyperswitch-logo-light.svg +++ b/docs/imgs/hyperswitch-logo-light.svg @@ -1,29 +1,21 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/docs/imgs/hyperswitch-product.png b/docs/imgs/hyperswitch-product.png deleted file mode 100644 index ad1c6c1ed035..000000000000 Binary files a/docs/imgs/hyperswitch-product.png and /dev/null differ diff --git a/docs/imgs/switch.png b/docs/imgs/switch.png index 9482864c99dd..6cf6b8689aa9 100644 Binary files a/docs/imgs/switch.png and b/docs/imgs/switch.png differ diff --git a/docs/try_local_system.md b/docs/try_local_system.md index cba62fd01f2e..07756463e740 100644 --- a/docs/try_local_system.md +++ b/docs/try_local_system.md @@ -15,6 +15,10 @@ Check the Table Of Contents to jump to the relevant section. - [Run hyperswitch using Docker Compose](#run-hyperswitch-using-docker-compose) - [Running additional services](#running-additional-services) - [Set up a development environment using Docker Compose](#set-up-a-development-environment-using-docker-compose) +- [Set up a Nix development environment](#set-up-a-nix-development-environment) + - [Install Nix](#install-nix) + - [Using external services through Nix](#using-external-services-through-nix) + - [Develop in a Nix environment (coming soon)](#develop-in-a-nix-environment-coming-soon) - [Set up a Rust environment and other dependencies](#set-up-a-rust-environment-and-other-dependencies) - [Set up dependencies on Ubuntu-based systems](#set-up-dependencies-on-ubuntu-based-systems) - [Set up dependencies on Windows (Ubuntu on WSL2)](#set-up-dependencies-on-windows-ubuntu-on-wsl2) @@ -56,7 +60,7 @@ Check the Table Of Contents to jump to the relevant section. 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) + [run the scheduler and monitoring services](#running-additional-services) 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: @@ -154,7 +158,7 @@ Once the services have been confirmed to be up and running, you can proceed with around 15 minutes. 5. (Optional) You can also choose to - [start the scheduler and/or monitoring services](#run-the-scheduler-and-monitoring-services) + [start the scheduler and/or monitoring services](#running-additional-services) in addition to the payments router. 6. Verify that the server is up and running by hitting the health endpoint: @@ -166,6 +170,43 @@ Once the services have been confirmed to be up and running, you can proceed with If the command returned a `200 OK` status code, proceed with [trying out our APIs](#try-out-our-apis). +## Set up a Nix development environment + +A Nix development environment simplifies the setup of required project dependencies. This is available for MacOS, Linux and WSL2 users. + +### Install nix + +We recommend that you install Nix using [the DetSys nix-installer][detsys-nixos-installer], which automatically enables flakes. + +As an **optional** next step, if you are interested in using Nix to manage your dotfiles and local packages, you can setup [nixos-unified-template][nixos-unified-template-repo]. + +### Using external services through Nix + +Once Nix is installed, you can use it to manage external services via `flakes`. More services will be added soon. + +- Run below command in hyperswitch directory + + ```shell + nix run .#ext-services + ``` + +This will start the following services using `process-compose` +- PostgreSQL + - Creates database and an user to be used by the application +- Redis + +### Develop in a Nix environment (coming soon) + +Nix development environment ensures all the required project dependencies, including both the tools and services are readily available, eliminating the need for manual setup. + +Run below command in hyperswitch directory + + ```shell + nix develop + ``` + +**NOTE:** This is a work in progress, and only a selected commands are available at the moment. Look in `flake.nix` (hyperswitch-shell) for a full list of packages. + ## Set up a Rust environment and other dependencies If you are using `nix`, please skip the setup dependencies step and jump to @@ -681,3 +722,5 @@ To explore more of our APIs, please check the remaining folders in the [refunds-create]: https://www.postman.com/hyperswitch/workspace/hyperswitch-development/request/25176162-4d1315c6-ac61-4411-8f7d-15d4e4e736a1 [refunds-retrieve]: https://www.postman.com/hyperswitch/workspace/hyperswitch-development/request/25176162-137d6260-24f7-4752-9e69-26b61b83df0d [connector-specific-details]: https://docs.google.com/spreadsheets/d/e/2PACX-1vQWHLza9m5iO4Ol-tEBx22_Nnq8Mb3ISCWI53nrinIGLK8eHYmHGnvXFXUXEut8AFyGyI9DipsYaBLG/pubhtml?gid=748960791&single=true +[detsys-nixos-installer]: https://nixos.asia/en/install +[nixos-unified-template-repo]: https://github.com/juspay/nixos-unified-template#on-non-nixos diff --git a/flake.lock b/flake.lock index 6f9551084751..6bdae435765b 100644 --- a/flake.lock +++ b/flake.lock @@ -107,11 +107,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1676569297, - "narHash": "sha256-2n4C4H3/U+3YbDrQB6xIw7AaLdFISCCFwOkcETAigqU=", + "lastModified": 1728888510, + "narHash": "sha256-nsNdSldaAyu6PE3YUA+YQLqUDJh+gRbBooMMekZJwvI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ac1f5b72a9e95873d1de0233fddcb56f99884b37", + "rev": "a3c0b3b21515f74fd2665903d4ce6bc4dc81c77c", "type": "github" }, "original": { @@ -137,12 +137,29 @@ "type": "github" } }, + "process-compose-flake": { + "locked": { + "lastModified": 1728868941, + "narHash": "sha256-yEMzxZfy+EE9gSqn++SyZeAVHXYupFT8Wyf99Z/CXXU=", + "owner": "Platonic-Systems", + "repo": "process-compose-flake", + "rev": "29301aec92d73c9b075fcfd06a6fb18665bfe6b5", + "type": "github" + }, + "original": { + "owner": "Platonic-Systems", + "repo": "process-compose-flake", + "type": "github" + } + }, "root": { "inputs": { "cargo2nix": "cargo2nix", "flake-parts": "flake-parts", "nixpkgs": "nixpkgs_2", - "rust-overlay": "rust-overlay_2" + "process-compose-flake": "process-compose-flake", + "rust-overlay": "rust-overlay_2", + "services-flake": "services-flake" } }, "rust-overlay": { @@ -187,6 +204,21 @@ "repo": "rust-overlay", "type": "github" } + }, + "services-flake": { + "locked": { + "lastModified": 1728811751, + "narHash": "sha256-IrwycNtt6jxJGCi+QJ8Bbzt9flg0vNeGLAR0KBbj4a8=", + "owner": "juspay", + "repo": "services-flake", + "rev": "e9f663036f3b1b1a12b0f136628ef93a8be92443", + "type": "github" + }, + "original": { + "owner": "juspay", + "repo": "services-flake", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index ad3de7e660b1..9516d0c81a7a 100644 --- a/flake.nix +++ b/flake.nix @@ -8,10 +8,14 @@ # TODO: Move away from these to https://github.com/juspay/rust-flake cargo2nix.url = "github:cargo2nix/cargo2nix/release-0.11.0"; rust-overlay.url = "github:oxalica/rust-overlay"; + + process-compose-flake.url = "github:Platonic-Systems/process-compose-flake"; + services-flake.url = "github:juspay/services-flake"; }; outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } { + imports = [ inputs.process-compose-flake.flakeModule ]; systems = inputs.nixpkgs.lib.systems.flakeExposed; perSystem = { self', pkgs, lib, system, ... }: let @@ -27,10 +31,10 @@ devShells.default = pkgs.mkShell { name = "hyperswitch-shell"; packages = with pkgs; [ + just + nixd openssl pkg-config - exa - fd rust-bin.stable.${rustVersion}.default ] ++ lib.optionals stdenv.isDarwin [ # arch might have issue finding these libs. @@ -38,6 +42,36 @@ frameworks.Foundation ]; }; + + /* For running external services + - Redis + - Postgres + */ + process-compose."ext-services" = + let + developmentToml = lib.importTOML ./config/development.toml; + databaseName = developmentToml.master_database.dbname; + databaseUser = developmentToml.master_database.username; + databasePass = developmentToml.master_database.password; + in + { + imports = [ inputs.services-flake.processComposeModules.default ]; + services.redis."r1".enable = true; + /* Postgres + - Create an user and grant all privileges + - Create a database + */ + services.postgres."p1" = { + enable = true; + initialScript = { + before = "CREATE USER ${databaseUser} WITH PASSWORD '${databasePass}' SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN;"; + after = "GRANT ALL PRIVILEGES ON DATABASE ${databaseName} to ${databaseUser};"; + }; + initialDatabases = [ + { name = databaseName; } + ]; + }; + }; }; }; } diff --git a/justfile b/justfile index 84df776c1b0d..43fca4afc893 100644 --- a/justfile +++ b/justfile @@ -9,6 +9,7 @@ fmt *FLAGS: cargo +nightly fmt {{ fmt_flags }} {{ FLAGS }} check_flags := '--all-targets' +v2_lints:= '-D warnings -Aunused -Aclippy::todo -Aclippy::diverging_sub_expression' alias c := check @@ -44,7 +45,7 @@ clippy_v2 *FLAGS: ')" set -x - cargo clippy {{ check_flags }} --no-default-features --features "${FEATURES}" {{ FLAGS }} + cargo clippy {{ check_flags }} --no-default-features --features "${FEATURES}" -- {{ v2_lints }} {{ FLAGS }} set +x check_v2 *FLAGS: @@ -60,7 +61,7 @@ check_v2 *FLAGS: ')" set -x - cargo check {{ check_flags }} --no-default-features --features "${FEATURES}" {{ FLAGS }} + cargo check {{ check_flags }} --no-default-features --features "${FEATURES}" -- {{ FLAGS }} set +x run_v2: diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index 45b5fe13146c..ec58ab08b878 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -35,6 +35,8 @@ jwt_secret = "secret" password_validity_in_days = 90 two_factor_auth_expiry_in_secs = 300 totp_issuer_name = "Hyperswitch" +force_two_factor_auth = false +force_cookies = true [locker] host = "" @@ -78,6 +80,7 @@ adyenplatform.base_url = "https://balanceplatform-api-test.adyen.com/" adyen.payout_base_url = "https://pal-test.adyen.com/" adyen.dispute_base_url = "https://ca-test.adyen.com/" airwallex.base_url = "https://api-demo.airwallex.com/" +amazonpay.base_url = "https://pay-api.amazon.com/v2" applepay.base_url = "https://apple-pay-gateway.apple.com/" authorizedotnet.base_url = "https://apitest.authorize.net/xml/v1/request.api" bambora.base_url = "https://api.na.bambora.com" @@ -97,9 +100,11 @@ cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" deutschebank.base_url = "https://testmerch.directpos.de/rest-api" +digitalvirgo.base_url = "https://dcb-integration-service-sandbox-external.staging.digitalvirgo.pl" dlocal.base_url = "https://sandbox.dlocal.com/" dummyconnector.base_url = "http://localhost:8080/dummy-connector" ebanx.base_url = "https://sandbox.ebanxpay.com/" +elavon.base_url = "https://api.demo.convergepay.com/VirtualMerchantDemo/" fiserv.base_url = "https://cert.api.fiservapps.com/" fiservemea.base_url = "https://prod.emea.api.fiservapps.com/sandbox" fiuu.base_url = "https://sandbox.merchant.razer.com/" @@ -112,7 +117,10 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" +inespay.base_url = "https://apiflow.inespay.com/san/v21" itaubank.base_url = "https://sandbox.devportal.itau.com.br/" +jpmorgan.base_url = "https://api-mock.payments.jpmorgan.com/api/v2" +jpmorgan.secondary_base_url="https://id.payments.jpmorgan.com" klarna.base_url = "https://api{{klarna_region}}.playground.klarna.com/" mifinity.base_url = "https://demo.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" @@ -122,6 +130,7 @@ netcetera.base_url = "https://{{merchant_endpoint_prefix}}.3ds-server.prev.netce nexinets.base_url = "https://apitest.payengine.de/v1" nexixpay.base_url = "https://xpaysandbox.nexigroup.com/api/phoenix-0.0/psp/api/v1" nmi.base_url = "https://secure.nmi.com/" +nomupay.base_url = "https://payout-api.sandbox.nomupay.com" noon.base_url = "https://api-test.noonpayments.com/" noon.key_mode = "Test" novalnet.base_url = "https://payport.novalnet.de/v2" @@ -141,6 +150,7 @@ powertranz.base_url = "https://staging.ptranz.com/api/" prophetpay.base_url = "https://ccm-thirdparty.cps.golf/" rapyd.base_url = "https://sandboxapi.rapyd.net" razorpay.base_url = "https://sandbox.juspay.in/" +redsys.base_url = "https://sis-t.redsys.es:25443/sis/realizarPago" riskified.base_url = "https://sandbox.riskified.com/api" shift4.base_url = "https://api.shift4.com/" signifyd.base_url = "https://api.signifyd.com/" @@ -155,11 +165,13 @@ stripe.base_url_file_upload = "https://files.stripe.com/" trustpay.base_url = "https://test-tpgw.trustpay.eu/" trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/" tsys.base_url = "https://stagegw.transnox.com/" +unified_authentication_service.base_url = "http://localhost:8000" volt.base_url = "https://api.sandbox.volt.io/" wellsfargo.base_url = "https://apitest.cybersource.com/" wellsfargopayout.base_url = "https://api-sandbox.wellsfargo.com/" worldline.base_url = "https://eu.sandbox.api-ingenico.com/" worldpay.base_url = "https://try.access.worldpay.com/" +xendit.base_url = "https://api.xendit.co" wise.base_url = "https://api.sandbox.transferwise.tech/" zen.base_url = "https://api.zen-test.com/" zen.secondary_base_url = "https://secure.zen-test.com/" @@ -177,6 +189,7 @@ cards = [ "adyen", "adyenplatform", "airwallex", + "amazonpay", "authorizedotnet", "bambora", "bamboraapac", @@ -192,9 +205,11 @@ cards = [ "cybersource", "datatrans", "deutschebank", + "digitalvirgo", "dlocal", "dummyconnector", "ebanx", + "elavon", "fiserv", "fiservemea", "fiuu", @@ -205,13 +220,16 @@ cards = [ "gpayments", "helcim", "iatapay", + "inespay", "itaubank", + "jpmorgan", "mollie", "multisafepay", "netcetera", "nexinets", "nexixpay", "nmi", + "nomupay", "noon", "novalnet", "nuvei", @@ -236,12 +254,14 @@ cards = [ "thunes", "trustpay", "tsys", + "unified_authentication_service", "volt", "wellsfargo", "wellsfargopayout", "wise", "worldline", "worldpay", + "xendit", "zen", "zsl", ] @@ -259,6 +279,10 @@ paypal = { country = "AU,NZ,CN,JP,HK,MY,TH,KR,PH,ID,AE,KW,BR,ES,GB,SE,NO,SK,AT,N klarna = { country = "AU,AT,BE,CA,CZ,DK,FI,FR,DE,GR,IE,IT,NO,PL,PT,RO,ES,SE,CH,NL,GB,US", currency = "AUD,EUR,CAD,CZK,DKK,NOK,PLN,RON,SEK,CHF,GBP,USD"} ideal = { country = "NL", currency = "EUR" } +[pm_filters.bambora] +credit = { country = "US,CA", currency = "USD" } +debit = { country = "US,CA", currency = "USD" } + [pm_filters.zen] credit = { not_available_flows = { capture_method = "manual" } } debit = { not_available_flows = { capture_method = "manual" } } @@ -308,19 +332,31 @@ discord_invite_url = "https://discord.gg/wJZ7DVW8mm" payout_eligibility = true [mandates.supported_payment_methods] -pay_later.klarna = {connector_list = "adyen"} -wallet.google_pay = {connector_list = "stripe,adyen,bankofamerica"} -wallet.apple_pay = {connector_list = "stripe,adyen,bankofamerica"} -wallet.paypal = {connector_list = "adyen"} -card.credit = {connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree"} -card.debit = {connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree"} -bank_debit.ach = { connector_list = "gocardless,adyen" } -bank_debit.becs = { connector_list = "gocardless" } -bank_debit.bacs = { connector_list = "adyen" } -bank_debit.sepa = { connector_list = "gocardless,adyen" } -bank_redirect.ideal = {connector_list = "stripe,adyen,globalpay"} -bank_redirect.sofort = {connector_list = "stripe,adyen,globalpay"} -bank_redirect.giropay = {connector_list = "adyen,globalpay"} +bank_debit.ach = { connector_list = "gocardless,adyen,stripe" } +bank_debit.becs = { connector_list = "gocardless,stripe,adyen" } +bank_debit.bacs = { connector_list = "stripe,gocardless" } +bank_debit.sepa = { connector_list = "gocardless,adyen,stripe,deutschebank" } +card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" +card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" +pay_later.klarna.connector_list = "adyen" +wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon,bankofamerica,nexinets,novalnet" +wallet.google_pay.connector_list = "stripe,adyen,cybersource,bankofamerica,noon,globalpay,multisafepay,novalnet" +wallet.paypal.connector_list = "adyen,globalpay,nexinets,novalnet,paypal" +wallet.momo.connector_list = "adyen" +wallet.kakao_pay.connector_list = "adyen" +wallet.go_pay.connector_list = "adyen" +wallet.gcash.connector_list = "adyen" +wallet.dana.connector_list = "adyen" +wallet.twint.connector_list = "adyen" +wallet.vipps.connector_list = "adyen" + +bank_redirect.ideal.connector_list = "stripe,adyen,globalpay,multisafepay,nexinets" +bank_redirect.sofort.connector_list = "stripe,adyen,globalpay" +bank_redirect.giropay.connector_list = "adyen,globalpay,multisafepay,nexinets" +bank_redirect.bancontact_card.connector_list="adyen,stripe" +bank_redirect.trustly.connector_list="adyen" +bank_redirect.open_banking_uk.connector_list="adyen" +bank_redirect.eps.connector_list="globalpay,nexinets" [cors] @@ -360,20 +396,26 @@ client_secret = "" partner_id = "" [unmasked_headers] -keys = "accept-language,user-agent" +keys = "accept-language,user-agent,x-profile-id" [multitenancy] enabled = false global_tenant = { schema = "public", redis_key_prefix = "" } -[multitenancy.tenants] -public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"} +[multitenancy.tenants.public] +base_url = "http://localhost:8080" +schema = "public" +redis_key_prefix = "" +clickhouse_database = "default" + +[multitenancy.tenants.public.user] +control_center_url = "http://localhost:9000" [email] sender_email = "example@example.com" aws_region = "" allowed_unverified_days = 1 -active_email_client = "SES" +active_email_client = "NO_EMAIL_CLIENT" recon_recipient_email = "recon@example.com" prod_intent_recipient_email = "business@example.com" diff --git a/migrations/2024-09-25-113851_increase_connector_transaction_id_length_in_payment_and_refund/down.sql b/migrations/2024-09-25-113851_increase_connector_transaction_id_length_in_payment_and_refund/down.sql new file mode 100644 index 000000000000..8008535f877c --- /dev/null +++ b/migrations/2024-09-25-113851_increase_connector_transaction_id_length_in_payment_and_refund/down.sql @@ -0,0 +1,11 @@ +ALTER TABLE payment_attempt +DROP COLUMN IF EXISTS connector_transaction_data; + +ALTER TABLE refund +DROP COLUMN IF EXISTS connector_refund_data; + +ALTER TABLE refund +DROP COLUMN IF EXISTS connector_transaction_data; + +ALTER TABLE captures +DROP COLUMN IF EXISTS connector_capture_data; \ No newline at end of file diff --git a/migrations/2024-09-25-113851_increase_connector_transaction_id_length_in_payment_and_refund/up.sql b/migrations/2024-09-25-113851_increase_connector_transaction_id_length_in_payment_and_refund/up.sql new file mode 100644 index 000000000000..adab6dd3edf7 --- /dev/null +++ b/migrations/2024-09-25-113851_increase_connector_transaction_id_length_in_payment_and_refund/up.sql @@ -0,0 +1,11 @@ +ALTER TABLE payment_attempt +ADD COLUMN IF NOT EXISTS connector_transaction_data VARCHAR(512); + +ALTER TABLE refund +ADD COLUMN IF NOT EXISTS connector_refund_data VARCHAR(512); + +ALTER TABLE refund +ADD COLUMN IF NOT EXISTS connector_transaction_data VARCHAR(512); + +ALTER TABLE captures +ADD COLUMN IF NOT EXISTS connector_capture_data VARCHAR(512); \ No newline at end of file diff --git a/migrations/2024-10-13-182546_add_connector_mandate_id_in_payment_attempt/down.sql b/migrations/2024-10-13-182546_add_connector_mandate_id_in_payment_attempt/down.sql new file mode 100644 index 000000000000..f4e6f2e2e262 --- /dev/null +++ b/migrations/2024-10-13-182546_add_connector_mandate_id_in_payment_attempt/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE + payment_attempt DROP COLUMN connector_mandate_detail; \ No newline at end of file diff --git a/migrations/2024-10-13-182546_add_connector_mandate_id_in_payment_attempt/up.sql b/migrations/2024-10-13-182546_add_connector_mandate_id_in_payment_attempt/up.sql new file mode 100644 index 000000000000..bb592f623d43 --- /dev/null +++ b/migrations/2024-10-13-182546_add_connector_mandate_id_in_payment_attempt/up.sql @@ -0,0 +1,5 @@ +-- Your SQL goes here +ALTER TABLE + payment_attempt +ADD + COLUMN connector_mandate_detail JSONB DEFAULT NULL; \ No newline at end of file diff --git a/migrations/2024-10-24-123318_update-entity-type-column-in-roles/down.sql b/migrations/2024-10-24-123318_update-entity-type-column-in-roles/down.sql new file mode 100644 index 000000000000..60dfa892e602 --- /dev/null +++ b/migrations/2024-10-24-123318_update-entity-type-column-in-roles/down.sql @@ -0,0 +1,4 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE roles ALTER COLUMN entity_type DROP DEFAULT; + +ALTER TABLE roles ALTER COLUMN entity_type DROP NOT NULL; \ No newline at end of file diff --git a/migrations/2024-10-24-123318_update-entity-type-column-in-roles/up.sql b/migrations/2024-10-24-123318_update-entity-type-column-in-roles/up.sql new file mode 100644 index 000000000000..564a026184ef --- /dev/null +++ b/migrations/2024-10-24-123318_update-entity-type-column-in-roles/up.sql @@ -0,0 +1,6 @@ +-- Your SQL goes here +UPDATE roles SET entity_type = 'merchant' WHERE entity_type IS NULL; + +ALTER TABLE roles ALTER COLUMN entity_type SET DEFAULT 'merchant'; + +ALTER TABLE roles ALTER COLUMN entity_type SET NOT NULL; \ No newline at end of file diff --git a/migrations/2024-10-26-105654_add_column_tenant_id_to_user_roles/down.sql b/migrations/2024-10-26-105654_add_column_tenant_id_to_user_roles/down.sql new file mode 100644 index 000000000000..fb9a32c0767b --- /dev/null +++ b/migrations/2024-10-26-105654_add_column_tenant_id_to_user_roles/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE user_roles DROP COLUMN IF EXISTS tenant_id; \ No newline at end of file diff --git a/migrations/2024-10-26-105654_add_column_tenant_id_to_user_roles/up.sql b/migrations/2024-10-26-105654_add_column_tenant_id_to_user_roles/up.sql new file mode 100644 index 000000000000..68e75f7f5d8b --- /dev/null +++ b/migrations/2024-10-26-105654_add_column_tenant_id_to_user_roles/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE user_roles ADD COLUMN IF NOT EXISTS tenant_id VARCHAR(64) NOT NULL DEFAULT 'public'; diff --git a/migrations/2024-10-28-125949_add_dispute_currency_column_in_dispute_table/down.sql b/migrations/2024-10-28-125949_add_dispute_currency_column_in_dispute_table/down.sql new file mode 100644 index 000000000000..052223ca0ae8 --- /dev/null +++ b/migrations/2024-10-28-125949_add_dispute_currency_column_in_dispute_table/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE dispute DROP COLUMN IF EXISTS dispute_currency; \ No newline at end of file diff --git a/migrations/2024-10-28-125949_add_dispute_currency_column_in_dispute_table/up.sql b/migrations/2024-10-28-125949_add_dispute_currency_column_in_dispute_table/up.sql new file mode 100644 index 000000000000..732909adfea2 --- /dev/null +++ b/migrations/2024-10-28-125949_add_dispute_currency_column_in_dispute_table/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE dispute ADD COLUMN IF NOT EXISTS dispute_currency "Currency"; \ No newline at end of file diff --git a/migrations/2024-11-06-121933_setup-themes-table/down.sql b/migrations/2024-11-06-121933_setup-themes-table/down.sql new file mode 100644 index 000000000000..4b590c34705e --- /dev/null +++ b/migrations/2024-11-06-121933_setup-themes-table/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` +DROP INDEX IF EXISTS themes_index; +DROP TABLE IF EXISTS themes; diff --git a/migrations/2024-11-06-121933_setup-themes-table/up.sql b/migrations/2024-11-06-121933_setup-themes-table/up.sql new file mode 100644 index 000000000000..3d84fcd81405 --- /dev/null +++ b/migrations/2024-11-06-121933_setup-themes-table/up.sql @@ -0,0 +1,17 @@ +-- Your SQL goes here +CREATE TABLE IF NOT EXISTS themes ( + theme_id VARCHAR(64) PRIMARY KEY, + tenant_id VARCHAR(64) NOT NULL, + org_id VARCHAR(64), + merchant_id VARCHAR(64), + profile_id VARCHAR(64), + created_at TIMESTAMP NOT NULL, + last_modified_at TIMESTAMP NOT NULL +); + +CREATE UNIQUE INDEX IF NOT EXISTS themes_index ON themes ( + tenant_id, + COALESCE(org_id, '0'), + COALESCE(merchant_id, '0'), + COALESCE(profile_id, '0') +); diff --git a/migrations/2024-11-14-084429_add_sca_exemption_field_to_payment_intent/down.sql b/migrations/2024-11-14-084429_add_sca_exemption_field_to_payment_intent/down.sql new file mode 100644 index 000000000000..f43aff7f567d --- /dev/null +++ b/migrations/2024-11-14-084429_add_sca_exemption_field_to_payment_intent/down.sql @@ -0,0 +1,4 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE payment_intent DROP COLUMN psd2_sca_exemption_type; + +DROP TYPE "ScaExemptionType"; \ No newline at end of file diff --git a/migrations/2024-11-14-084429_add_sca_exemption_field_to_payment_intent/up.sql b/migrations/2024-11-14-084429_add_sca_exemption_field_to_payment_intent/up.sql new file mode 100644 index 000000000000..6fa361a48022 --- /dev/null +++ b/migrations/2024-11-14-084429_add_sca_exemption_field_to_payment_intent/up.sql @@ -0,0 +1,6 @@ +CREATE TYPE "ScaExemptionType" AS ENUM ( + 'low_value', + 'transaction_risk_analysis' +); + +ALTER TABLE payment_intent ADD COLUMN IF NOT EXISTS psd2_sca_exemption_type "ScaExemptionType"; \ No newline at end of file diff --git a/migrations/2024-11-15-171347_add_capture_method_sequential_automatic/down.sql b/migrations/2024-11-15-171347_add_capture_method_sequential_automatic/down.sql new file mode 100644 index 000000000000..c7c9cbeb4017 --- /dev/null +++ b/migrations/2024-11-15-171347_add_capture_method_sequential_automatic/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +SELECT 1; \ No newline at end of file diff --git a/migrations/2024-11-15-171347_add_capture_method_sequential_automatic/up.sql b/migrations/2024-11-15-171347_add_capture_method_sequential_automatic/up.sql new file mode 100644 index 000000000000..7a6be5e26582 --- /dev/null +++ b/migrations/2024-11-15-171347_add_capture_method_sequential_automatic/up.sql @@ -0,0 +1,11 @@ +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_enum + WHERE enumlabel = 'sequential_automatic' + AND enumtypid = (SELECT oid FROM pg_type WHERE typname = 'CaptureMethod') + ) THEN + ALTER TYPE "CaptureMethod" ADD VALUE 'sequential_automatic' AFTER 'manual'; + END IF; +END $$; diff --git a/migrations/2024-11-20-110014_add-entity-type-and-theme-name-in-themes/down.sql b/migrations/2024-11-20-110014_add-entity-type-and-theme-name-in-themes/down.sql new file mode 100644 index 000000000000..a56426b60c09 --- /dev/null +++ b/migrations/2024-11-20-110014_add-entity-type-and-theme-name-in-themes/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE themes DROP COLUMN IF EXISTS entity_type; +ALTER TABLE themes DROP COLUMN IF EXISTS theme_name; diff --git a/migrations/2024-11-20-110014_add-entity-type-and-theme-name-in-themes/up.sql b/migrations/2024-11-20-110014_add-entity-type-and-theme-name-in-themes/up.sql new file mode 100644 index 000000000000..924385d45d68 --- /dev/null +++ b/migrations/2024-11-20-110014_add-entity-type-and-theme-name-in-themes/up.sql @@ -0,0 +1,3 @@ +-- Your SQL goes here +ALTER TABLE themes ADD COLUMN IF NOT EXISTS entity_type VARCHAR(64) NOT NULL; +ALTER TABLE themes ADD COLUMN IF NOT EXISTS theme_name VARCHAR(64) NOT NULL; diff --git a/migrations/2024-11-22-091336_add_split_payments_in_payment_intent/down.sql b/migrations/2024-11-22-091336_add_split_payments_in_payment_intent/down.sql new file mode 100644 index 000000000000..000b02a89ce1 --- /dev/null +++ b/migrations/2024-11-22-091336_add_split_payments_in_payment_intent/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE payment_intent DROP COLUMN IF EXISTS split_payments; diff --git a/migrations/2024-11-22-091336_add_split_payments_in_payment_intent/up.sql b/migrations/2024-11-22-091336_add_split_payments_in_payment_intent/up.sql new file mode 100644 index 000000000000..b513f864444d --- /dev/null +++ b/migrations/2024-11-22-091336_add_split_payments_in_payment_intent/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE payment_intent ADD COLUMN IF NOT EXISTS split_payments jsonb; \ No newline at end of file diff --git a/migrations/2024-11-24-104438_add_error_category_col_to_gsm/down.sql b/migrations/2024-11-24-104438_add_error_category_col_to_gsm/down.sql new file mode 100644 index 000000000000..609afbdfd753 --- /dev/null +++ b/migrations/2024-11-24-104438_add_error_category_col_to_gsm/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE gateway_status_map DROP COLUMN error_category; diff --git a/migrations/2024-11-24-104438_add_error_category_col_to_gsm/up.sql b/migrations/2024-11-24-104438_add_error_category_col_to_gsm/up.sql new file mode 100644 index 000000000000..298f6d263aa2 --- /dev/null +++ b/migrations/2024-11-24-104438_add_error_category_col_to_gsm/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE gateway_status_map ADD COLUMN error_category VARCHAR(64); diff --git a/migrations/2024-11-28-103344_add_split_refunds/down.sql b/migrations/2024-11-28-103344_add_split_refunds/down.sql new file mode 100644 index 000000000000..6eeb56a5e096 --- /dev/null +++ b/migrations/2024-11-28-103344_add_split_refunds/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE refund DROP COLUMN IF EXISTS split_refunds; diff --git a/migrations/2024-11-28-103344_add_split_refunds/up.sql b/migrations/2024-11-28-103344_add_split_refunds/up.sql new file mode 100644 index 000000000000..4f36a1cb77f3 --- /dev/null +++ b/migrations/2024-11-28-103344_add_split_refunds/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE refund ADD COLUMN IF NOT EXISTS split_refunds jsonb; \ No newline at end of file diff --git a/migrations/2024-12-02-095127_add_new_table_dynamic_routing_stats/down.sql b/migrations/2024-12-02-095127_add_new_table_dynamic_routing_stats/down.sql new file mode 100644 index 000000000000..993a082c2344 --- /dev/null +++ b/migrations/2024-12-02-095127_add_new_table_dynamic_routing_stats/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` +DROP TABLE IF EXISTS dynamic_routing_stats; +DROP TYPE IF EXISTS "SuccessBasedRoutingConclusiveState"; diff --git a/migrations/2024-12-02-095127_add_new_table_dynamic_routing_stats/up.sql b/migrations/2024-12-02-095127_add_new_table_dynamic_routing_stats/up.sql new file mode 100644 index 000000000000..6b37787d7224 --- /dev/null +++ b/migrations/2024-12-02-095127_add_new_table_dynamic_routing_stats/up.sql @@ -0,0 +1,26 @@ +--- Your SQL goes here +CREATE TYPE "SuccessBasedRoutingConclusiveState" AS ENUM( + 'true_positive', + 'false_positive', + 'true_negative', + 'false_negative' +); + +CREATE TABLE IF NOT EXISTS dynamic_routing_stats ( + payment_id VARCHAR(64) NOT NULL, + attempt_id VARCHAR(64) NOT NULL, + merchant_id VARCHAR(64) NOT NULL, + profile_id VARCHAR(64) NOT NULL, + amount BIGINT NOT NULL, + success_based_routing_connector VARCHAR(64) NOT NULL, + payment_connector VARCHAR(64) NOT NULL, + currency "Currency", + payment_method VARCHAR(64), + capture_method "CaptureMethod", + authentication_type "AuthenticationType", + payment_status "AttemptStatus" NOT NULL, + conclusive_classification "SuccessBasedRoutingConclusiveState" NOT NULL, + created_at TIMESTAMP NOT NULL, + PRIMARY KEY(attempt_id, merchant_id) +); +CREATE INDEX profile_id_index ON dynamic_routing_stats (profile_id); diff --git a/migrations/2024-12-02-110129_update-user-role-entity-type/down.sql b/migrations/2024-12-02-110129_update-user-role-entity-type/down.sql new file mode 100644 index 000000000000..dff040ebc9e6 --- /dev/null +++ b/migrations/2024-12-02-110129_update-user-role-entity-type/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +UPDATE user_roles SET entity_type = NULL WHERE version = 'v1'; \ No newline at end of file diff --git a/migrations/2024-12-02-110129_update-user-role-entity-type/up.sql b/migrations/2024-12-02-110129_update-user-role-entity-type/up.sql new file mode 100644 index 000000000000..b982a8b13730 --- /dev/null +++ b/migrations/2024-12-02-110129_update-user-role-entity-type/up.sql @@ -0,0 +1,11 @@ +-- Your SQL goes here +-- Incomplete migration, also run migrations/2024-12-13-080558_entity-id-backfill-for-user-roles +UPDATE user_roles +SET + entity_type = CASE + WHEN role_id = 'org_admin' THEN 'organization' + ELSE 'merchant' + END +WHERE + version = 'v1' + AND entity_type IS NULL; \ No newline at end of file diff --git a/migrations/2024-12-03-072318_platform_merchant_account/down.sql b/migrations/2024-12-03-072318_platform_merchant_account/down.sql new file mode 100644 index 000000000000..342380e09333 --- /dev/null +++ b/migrations/2024-12-03-072318_platform_merchant_account/down.sql @@ -0,0 +1,4 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE merchant_account DROP COLUMN IF EXISTS is_platform_account; + +ALTER TABLE payment_intent DROP COLUMN IF EXISTS platform_merchant_id; diff --git a/migrations/2024-12-03-072318_platform_merchant_account/up.sql b/migrations/2024-12-03-072318_platform_merchant_account/up.sql new file mode 100644 index 000000000000..cd07e163d7c8 --- /dev/null +++ b/migrations/2024-12-03-072318_platform_merchant_account/up.sql @@ -0,0 +1,4 @@ +-- Your SQL goes here +ALTER TABLE merchant_account ADD COLUMN IF NOT EXISTS is_platform_account BOOL NOT NULL DEFAULT FALSE; + +ALTER TABLE payment_intent ADD COLUMN IF NOT EXISTS platform_merchant_id VARCHAR(64); diff --git a/migrations/2024-12-04-072648_add_is_click_to_pay_enabled/down.sql b/migrations/2024-12-04-072648_add_is_click_to_pay_enabled/down.sql new file mode 100644 index 000000000000..8ff46e69d6f3 --- /dev/null +++ b/migrations/2024-12-04-072648_add_is_click_to_pay_enabled/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE business_profile DROP COLUMN IF EXISTS is_click_to_pay_enabled; \ No newline at end of file diff --git a/migrations/2024-12-04-072648_add_is_click_to_pay_enabled/up.sql b/migrations/2024-12-04-072648_add_is_click_to_pay_enabled/up.sql new file mode 100644 index 000000000000..3adb84258b79 --- /dev/null +++ b/migrations/2024-12-04-072648_add_is_click_to_pay_enabled/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE business_profile ADD COLUMN IF NOT EXISTS is_click_to_pay_enabled BOOLEAN NOT NULL DEFAULT FALSE; \ No newline at end of file diff --git a/migrations/2024-12-05-115544_add-service-details/down.sql b/migrations/2024-12-05-115544_add-service-details/down.sql new file mode 100644 index 000000000000..1c42146adfc1 --- /dev/null +++ b/migrations/2024-12-05-115544_add-service-details/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE authentication DROP COLUMN IF EXISTS service_details; \ No newline at end of file diff --git a/migrations/2024-12-05-115544_add-service-details/up.sql b/migrations/2024-12-05-115544_add-service-details/up.sql new file mode 100644 index 000000000000..3555647a987e --- /dev/null +++ b/migrations/2024-12-05-115544_add-service-details/up.sql @@ -0,0 +1,4 @@ +-- Your SQL goes here +ALTER TABLE authentication +ADD COLUMN IF NOT EXISTS service_details JSONB +DEFAULT NULL; \ No newline at end of file diff --git a/migrations/2024-12-05-131123_add-email-theme-data-in-themes/down.sql b/migrations/2024-12-05-131123_add-email-theme-data-in-themes/down.sql new file mode 100644 index 000000000000..34732b5dd695 --- /dev/null +++ b/migrations/2024-12-05-131123_add-email-theme-data-in-themes/down.sql @@ -0,0 +1,6 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE themes DROP COLUMN IF EXISTS email_primary_color; +ALTER TABLE themes DROP COLUMN IF EXISTS email_foreground_color; +ALTER TABLE themes DROP COLUMN IF EXISTS email_background_color; +ALTER TABLE themes DROP COLUMN IF EXISTS email_entity_name; +ALTER TABLE themes DROP COLUMN IF EXISTS email_entity_logo_url; diff --git a/migrations/2024-12-05-131123_add-email-theme-data-in-themes/up.sql b/migrations/2024-12-05-131123_add-email-theme-data-in-themes/up.sql new file mode 100644 index 000000000000..1004302ab7f6 --- /dev/null +++ b/migrations/2024-12-05-131123_add-email-theme-data-in-themes/up.sql @@ -0,0 +1,6 @@ +-- Your SQL goes here +ALTER TABLE themes ADD COLUMN IF NOT EXISTS email_primary_color VARCHAR(64) NOT NULL DEFAULT '#006DF9'; +ALTER TABLE themes ADD COLUMN IF NOT EXISTS email_foreground_color VARCHAR(64) NOT NULL DEFAULT '#000000'; +ALTER TABLE themes ADD COLUMN IF NOT EXISTS email_background_color VARCHAR(64) NOT NULL DEFAULT '#FFFFFF'; +ALTER TABLE themes ADD COLUMN IF NOT EXISTS email_entity_name VARCHAR(64) NOT NULL DEFAULT 'Hyperswitch'; +ALTER TABLE themes ADD COLUMN IF NOT EXISTS email_entity_logo_url TEXT NOT NULL DEFAULT 'https://app.hyperswitch.io/email-assets/HyperswitchLogo.png'; diff --git a/migrations/2024-12-11-092649_add-authentication-product-ids-in-business-profile/down.sql b/migrations/2024-12-11-092649_add-authentication-product-ids-in-business-profile/down.sql new file mode 100644 index 000000000000..a353beb4e403 --- /dev/null +++ b/migrations/2024-12-11-092649_add-authentication-product-ids-in-business-profile/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE business_profile +DROP COLUMN IF EXISTS authentication_product_ids diff --git a/migrations/2024-12-11-092649_add-authentication-product-ids-in-business-profile/up.sql b/migrations/2024-12-11-092649_add-authentication-product-ids-in-business-profile/up.sql new file mode 100644 index 000000000000..4e94da05c0e2 --- /dev/null +++ b/migrations/2024-12-11-092649_add-authentication-product-ids-in-business-profile/up.sql @@ -0,0 +1,3 @@ +-- Your SQL goes here +ALTER TABLE business_profile +ADD COLUMN IF NOT EXISTS authentication_product_ids JSONB NULL; diff --git a/migrations/2024-12-13-080558_entity-id-backfill-for-user-roles/down.sql b/migrations/2024-12-13-080558_entity-id-backfill-for-user-roles/down.sql new file mode 100644 index 000000000000..fb372f88a12b --- /dev/null +++ b/migrations/2024-12-13-080558_entity-id-backfill-for-user-roles/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +UPDATE user_roles SET entity_id = NULL WHERE version = 'v1'; \ No newline at end of file diff --git a/migrations/2024-12-13-080558_entity-id-backfill-for-user-roles/up.sql b/migrations/2024-12-13-080558_entity-id-backfill-for-user-roles/up.sql new file mode 100644 index 000000000000..7de41d880db6 --- /dev/null +++ b/migrations/2024-12-13-080558_entity-id-backfill-for-user-roles/up.sql @@ -0,0 +1,10 @@ +-- Your SQL goes here +UPDATE user_roles +SET + entity_id = CASE + WHEN role_id = 'org_admin' THEN org_id + ELSE merchant_id + END +WHERE + version = 'v1' + AND entity_id IS NULL; \ No newline at end of file diff --git a/migrations/2024-12-16-111228_add_new_col_payment_method_type_in_dynamic_routing_stats/down.sql b/migrations/2024-12-16-111228_add_new_col_payment_method_type_in_dynamic_routing_stats/down.sql new file mode 100644 index 000000000000..bc2f40c91d21 --- /dev/null +++ b/migrations/2024-12-16-111228_add_new_col_payment_method_type_in_dynamic_routing_stats/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE dynamic_routing_stats +DROP COLUMN IF EXISTS payment_method_type; diff --git a/migrations/2024-12-16-111228_add_new_col_payment_method_type_in_dynamic_routing_stats/up.sql b/migrations/2024-12-16-111228_add_new_col_payment_method_type_in_dynamic_routing_stats/up.sql new file mode 100644 index 000000000000..2b5af8090f0d --- /dev/null +++ b/migrations/2024-12-16-111228_add_new_col_payment_method_type_in_dynamic_routing_stats/up.sql @@ -0,0 +1,3 @@ +-- Your SQL goes here +ALTER TABLE dynamic_routing_stats +ADD COLUMN IF NOT EXISTS payment_method_type VARCHAR(64); diff --git a/migrations/2024-12-17-141811_add_relay_table/down.sql b/migrations/2024-12-17-141811_add_relay_table/down.sql new file mode 100644 index 000000000000..47b6682c6791 --- /dev/null +++ b/migrations/2024-12-17-141811_add_relay_table/down.sql @@ -0,0 +1,7 @@ +-- This file should undo anything in `up.sql` +DROP TABLE relay; + +DROP TYPE IF EXISTS "RelayStatus"; + +DROP TYPE IF EXISTS "RelayType"; + diff --git a/migrations/2024-12-17-141811_add_relay_table/up.sql b/migrations/2024-12-17-141811_add_relay_table/up.sql new file mode 100644 index 000000000000..e9f0017bd049 --- /dev/null +++ b/migrations/2024-12-17-141811_add_relay_table/up.sql @@ -0,0 +1,22 @@ +-- Your SQL goes here +CREATE TYPE "RelayStatus" AS ENUM ('created', 'pending', 'failure', 'success'); + +CREATE TYPE "RelayType" AS ENUM ('refund'); + +CREATE TABLE relay ( + id VARCHAR(64) PRIMARY KEY, + connector_resource_id VARCHAR(128) NOT NULL, + connector_id VARCHAR(64) NOT NULL, + profile_id VARCHAR(64) NOT NULL, + merchant_id VARCHAR(64) NOT NULL, + relay_type "RelayType" NOT NULL, + request_data JSONB DEFAULT NULL, + status "RelayStatus" NOT NULL, + connector_reference_id VARCHAR(128), + error_code VARCHAR(64), + error_message TEXT, + created_at TIMESTAMP NOT NULL DEFAULT now()::TIMESTAMP, + modified_at TIMESTAMP NOT NULL DEFAULT now()::TIMESTAMP, + response_data JSONB DEFAULT NULL +); + diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/Payments - Confirm/event.test.js b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/Payments - Confirm/event.test.js index e03c186a82e3..6ca7b398c74c 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/Payments - Confirm/event.test.js +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/Payments - Confirm/event.test.js @@ -81,13 +81,13 @@ if (jsonData?.error?.type) { ); } -// Response body should have value "A payment token or payment method data is required" for "message" +// Response body should have value "A payment token or payment method data or ctp service details is required" for "message" if (jsonData?.error?.message) { pm.test( "[POST]::/payments - Content check if value for 'error.message' matches 'connector_error'", function () { pm.expect(jsonData.error.message).to.eql( - "A payment token or payment method data is required", + "A payment token or payment method data or ctp service details is required", ); }, ); diff --git a/postman/collection-dir/paypal/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/Payments - Confirm/event.test.js b/postman/collection-dir/paypal/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/Payments - Confirm/event.test.js index e03c186a82e3..6ca7b398c74c 100644 --- a/postman/collection-dir/paypal/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/Payments - Confirm/event.test.js +++ b/postman/collection-dir/paypal/Flow Testcases/Variation Cases/Scenario2-Confirming the payment without PMD/Payments - Confirm/event.test.js @@ -81,13 +81,13 @@ if (jsonData?.error?.type) { ); } -// Response body should have value "A payment token or payment method data is required" for "message" +// Response body should have value "A payment token or payment method data or ctp service details is required" for "message" if (jsonData?.error?.message) { pm.test( "[POST]::/payments - Content check if value for 'error.message' matches 'connector_error'", function () { pm.expect(jsonData.error.message).to.eql( - "A payment token or payment method data is required", + "A payment token or payment method data or ctp service details is required", ); }, ); diff --git a/postman/collection-json/checkout.postman_collection.json b/postman/collection-json/checkout.postman_collection.json index f01d5d2fe758..33bfcef8aa90 100644 --- a/postman/collection-json/checkout.postman_collection.json +++ b/postman/collection-json/checkout.postman_collection.json @@ -12186,13 +12186,13 @@ " );", "}", "", - "// Response body should have value \"A payment token or payment method data is required\" for \"message\"", + "// Response body should have value \"A payment token or payment method data or ctp service details is required\" for \"message\"", "if (jsonData?.error?.message) {", " pm.test(", " \"[POST]::/payments - Content check if value for 'error.message' matches 'connector_error'\",", " function () {", " pm.expect(jsonData.error.message).to.eql(", - " \"A payment token or payment method data is required\",", + " \"A payment token or payment method data or ctp service details is required\",", " );", " },", " );", diff --git a/postman/collection-json/paypal.postman_collection.json b/postman/collection-json/paypal.postman_collection.json index b4309b2b4a00..50abbd7dd8f2 100644 --- a/postman/collection-json/paypal.postman_collection.json +++ b/postman/collection-json/paypal.postman_collection.json @@ -6137,13 +6137,13 @@ " );", "}", "", - "// Response body should have value \"A payment token or payment method data is required\" for \"message\"", + "// Response body should have value \"A payment token or payment method data or ctp service details is required\" for \"message\"", "if (jsonData?.error?.message) {", " pm.test(", " \"[POST]::/payments - Content check if value for 'error.message' matches 'connector_error'\",", " function () {", " pm.expect(jsonData.error.message).to.eql(", - " \"A payment token or payment method data is required\",", + " \"A payment token or payment method data or ctp service details is required\",", " );", " },", " );", diff --git a/proto/elimination_rate.proto b/proto/elimination_rate.proto new file mode 100644 index 000000000000..c5f10597ade2 --- /dev/null +++ b/proto/elimination_rate.proto @@ -0,0 +1,67 @@ +syntax = "proto3"; +package elimination; + +service EliminationAnalyser { + rpc GetEliminationStatus (EliminationRequest) returns (EliminationResponse); + + rpc UpdateEliminationBucket (UpdateEliminationBucketRequest) returns (UpdateEliminationBucketResponse); + + rpc InvalidateBucket (InvalidateBucketRequest) returns (InvalidateBucketResponse); +} + +// API-1 types +message EliminationRequest { + string id = 1; + string params = 2; + repeated string labels = 3; + EliminationBucketConfig config = 4; +} + +message EliminationBucketConfig { + uint64 bucket_size = 1; + uint64 bucket_leak_interval_in_secs = 2; +} + +message EliminationResponse { + repeated LabelWithStatus labels_with_status = 1; +} + +message LabelWithStatus { + string label = 1; + bool is_eliminated = 2; + string bucket_name = 3; +} + +// API-2 types +message UpdateEliminationBucketRequest { + string id = 1; + string params = 2; + repeated LabelWithBucketName labels_with_bucket_name = 3; + EliminationBucketConfig config = 4; +} + +message LabelWithBucketName { + string label = 1; + string bucket_name = 2; +} + +message UpdateEliminationBucketResponse { + enum UpdationStatus { + BUCKET_UPDATION_SUCCEEDED = 0; + BUCKET_UPDATION_FAILED = 1; + } + UpdationStatus status = 1; +} + +// API-3 types +message InvalidateBucketRequest { + string id = 1; +} + +message InvalidateBucketResponse { + enum InvalidationStatus { + BUCKET_INVALIDATION_SUCCEEDED = 0; + BUCKET_INVALIDATION_FAILED = 1; + } + InvalidationStatus status = 1; +} \ No newline at end of file diff --git a/proto/health_check.proto b/proto/health_check.proto new file mode 100644 index 000000000000..b246f59f6906 --- /dev/null +++ b/proto/health_check.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +package grpc.health.v1; + +message HealthCheckRequest { + string service = 1; +} + +message HealthCheckResponse { + enum ServingStatus { + UNKNOWN = 0; + SERVING = 1; + NOT_SERVING = 2; + SERVICE_UNKNOWN = 3; // Used only by the Watch method. + } + ServingStatus status = 1; +} + +service Health { + rpc Check(HealthCheckRequest) returns (HealthCheckResponse); +} \ No newline at end of file diff --git a/proto/success_rate.proto b/proto/success_rate.proto index 8018f6d5fe48..38e56e36c0ff 100644 --- a/proto/success_rate.proto +++ b/proto/success_rate.proto @@ -1,57 +1,68 @@ syntax = "proto3"; package success_rate; - service SuccessRateCalculator { - rpc FetchSuccessRate (CalSuccessRateRequest) returns (CalSuccessRateResponse); - - rpc UpdateSuccessRateWindow (UpdateSuccessRateWindowRequest) returns (UpdateSuccessRateWindowResponse); - } - - // API-1 types - message CalSuccessRateRequest { - string id = 1; - string params = 2; - repeated string labels = 3; - CalSuccessRateConfig config = 4; - } - - message CalSuccessRateConfig { - uint32 min_aggregates_size = 1; - double default_success_rate = 2; - } - - message CalSuccessRateResponse { - repeated LabelWithScore labels_with_score = 1; - } - - message LabelWithScore { - double score = 1; - string label = 2; - } +service SuccessRateCalculator { + rpc FetchSuccessRate (CalSuccessRateRequest) returns (CalSuccessRateResponse); + + rpc UpdateSuccessRateWindow (UpdateSuccessRateWindowRequest) returns (UpdateSuccessRateWindowResponse); + + rpc InvalidateWindows (InvalidateWindowsRequest) returns (InvalidateWindowsResponse); +} + +// API-1 types +message CalSuccessRateRequest { + string id = 1; + string params = 2; + repeated string labels = 3; + CalSuccessRateConfig config = 4; +} + +message CalSuccessRateConfig { + uint32 min_aggregates_size = 1; + double default_success_rate = 2; +} + +message CalSuccessRateResponse { + repeated LabelWithScore labels_with_score = 1; +} + +message LabelWithScore { + double score = 1; + string label = 2; +} // API-2 types - message UpdateSuccessRateWindowRequest { - string id = 1; - string params = 2; - repeated LabelWithStatus labels_with_status = 3; - UpdateSuccessRateWindowConfig config = 4; - } - - message LabelWithStatus { - string label = 1; - bool status = 2; - } - - message UpdateSuccessRateWindowConfig { - uint32 max_aggregates_size = 1; - CurrentBlockThreshold current_block_threshold = 2; - } - - message CurrentBlockThreshold { - optional uint64 duration_in_mins = 1; - uint64 max_total_count = 2; - } - - message UpdateSuccessRateWindowResponse { - string message = 1; - } \ No newline at end of file +message UpdateSuccessRateWindowRequest { + string id = 1; + string params = 2; + repeated LabelWithStatus labels_with_status = 3; + UpdateSuccessRateWindowConfig config = 4; +} + +message LabelWithStatus { + string label = 1; + bool status = 2; +} + +message UpdateSuccessRateWindowConfig { + uint32 max_aggregates_size = 1; + CurrentBlockThreshold current_block_threshold = 2; +} + +message CurrentBlockThreshold { + optional uint64 duration_in_mins = 1; + uint64 max_total_count = 2; +} + +message UpdateSuccessRateWindowResponse { + string message = 1; +} + + // API-3 types +message InvalidateWindowsRequest { + string id = 1; +} + +message InvalidateWindowsResponse { + string message = 1; +} \ No newline at end of file diff --git a/scripts/add_connector.sh b/scripts/add_connector.sh index fb7252833d5f..108e9d882635 100755 --- a/scripts/add_connector.sh +++ b/scripts/add_connector.sh @@ -6,9 +6,9 @@ function find_prev_connector() { git checkout $self cp $self $self.tmp # Add new connector to existing list and sort it - connectors=(aci adyen adyenplatform airwallex applepay authorizedotnet bambora bamboraapac bankofamerica billwerk bitpay bluesnap boku braintree cashtocode checkout coinbase cryptopay cybersource datatrans deutschebank dlocal dummyconnector ebanx fiserv fiservemea fiuu forte globalpay globepay gocardless gpayments helcim iatapay itaubank klarna mifinity mollie multisafepay netcetera nexinets nexixpay noon novalnet nuvei opayo opennode paybox payeezy payme payone paypal payu placetopay plaid powertranz prophetpay rapyd razorpay shift4 square stax stripe taxjar threedsecureio thunes trustpay tsys volt wellsfargo wellsfargopayout wise worldline worldpay zsl "$1") + connectors=(aci adyen adyenplatform airwallex amazonpay applepay authorizedotnet bambora bamboraapac bankofamerica billwerk bitpay bluesnap boku braintree cashtocode checkout coinbase cryptopay cybersource datatrans deutschebank digitalvirgo dlocal dummyconnector ebanx elavon fiserv fiservemea fiuu forte globalpay globepay gocardless gpayments helcim iatapay inespay itaubank jpmorgan klarna mifinity mollie multisafepay netcetera nexinets nexixpay nomupay noon novalnet nuvei opayo opennode paybox payeezy payme payone paypal payu placetopay plaid powertranz prophetpay rapyd razorpay redsys shift4 square stax stripe taxjar threedsecureio thunes trustpay tsys unified_authentication_service volt wellsfargo wellsfargopayout wise worldline worldpay xendit zsl "$1") IFS=$'\n' sorted=($(sort <<<"${connectors[*]}")); unset IFS - res=`echo ${sorted[@]}` + res="$(echo ${sorted[@]})" sed -i'' -e "s/^ connectors=.*/ connectors=($res \"\$1\")/" $self.tmp for i in "${!sorted[@]}"; do if [ "${sorted[$i]}" = "$1" ] && [ $i != "0" ]; then @@ -45,7 +45,7 @@ cd $SCRIPT/.. # Remove template files if already created for this connector rm -rf $conn/$payment_gateway $conn/$payment_gateway.rs -git checkout $conn.rs $src/types/api.rs $src/configs/settings.rs config/development.toml config/docker_compose.toml config/config.example.toml loadtest/config/development.toml crates/api_models/src/enums.rs crates/euclid/src/enums.rs crates/api_models/src/routing.rs $src/core/payments/flows.rs crates/common_enums/src/enums.rs $src/types/transformers.rs $src/core/admin.rs +git checkout $conn.rs $src/types/api.rs $src/configs/settings.rs config/development.toml config/docker_compose.toml config/config.example.toml loadtest/config/development.toml crates/api_models/src/connector_enums.rs crates/euclid/src/enums.rs crates/api_models/src/routing.rs $src/core/payments/flows.rs crates/common_enums/src/connector_enums.rs crates/common_enums/src/connector_enums.rs-e $src/types/transformers.rs $src/core/admin.rs # Add enum for this connector in required places previous_connector='' @@ -59,17 +59,17 @@ sed -i'' -e "s|$previous_connector_camelcase \(.*\)|$previous_connector_camelcas sed -i'' -e "s/pub $previous_connector: \(.*\)/pub $previous_connector: \1\n\tpub ${payment_gateway}: ConnectorParams,/" crates/hyperswitch_interfaces/src/configs.rs sed -i'' -e "s|$previous_connector.base_url \(.*\)|$previous_connector.base_url \1\n${payment_gateway}.base_url = \"$base_url\"|" config/development.toml config/docker_compose.toml config/config.example.toml loadtest/config/development.toml sed -r -i'' -e "s/\"$previous_connector\",/\"$previous_connector\",\n \"${payment_gateway}\",/" config/development.toml config/docker_compose.toml config/config.example.toml loadtest/config/development.toml -sed -i '' -e "s/\(pub enum Connector {\)/\1\n\t${payment_gateway_camelcase},/" crates/api_models/src/enums.rs -sed -i '' -e "/\/\/ Add Separate authentication support for connectors/{N;s/\(.*\)\n/\1\n\t\t\t| Self::${payment_gateway_camelcase}\n/;}" crates/api_models/src/enums.rs +sed -i '' -e "s/\(pub enum Connector {\)/\1\n\t${payment_gateway_camelcase},/" crates/api_models/src/connector_enums.rs +sed -i '' -e "/\/\/ Add Separate authentication support for connectors/{N;s/\(.*\)\n/\1\n\t\t\t| Self::${payment_gateway_camelcase}\n/;}" crates/api_models/src/connector_enums.rs sed -i '' -e "s/\(match connector_name {\)/\1\n\t\tapi_enums::Connector::${payment_gateway_camelcase} => {${payment_gateway}::transformers::${payment_gateway_camelcase}AuthType::try_from(val)?;Ok(())}/" $src/core/admin.rs -sed -i'' -e "s/\(pub enum RoutableConnectors {\)/\1\n\t${payment_gateway_camelcase},/" crates/common_enums/src/enums.rs +sed -i'' -e "s/\(pub enum RoutableConnectors {\)/\1\n\t${payment_gateway_camelcase},/" crates/common_enums/src/connector_enums.rs sed -i '' -e "s/\(pub enum Connector {\)/\1\n\t${payment_gateway_camelcase},/" crates/euclid/src/enums.rs sed -i'' -e "s|$previous_connector_camelcase \(.*\)|$previous_connector_camelcase \1\n\t\t\tapi_enums::Connector::${payment_gateway_camelcase} => Self::${payment_gateway_camelcase},|" $src/types/transformers.rs sed -i'' -e "s/^default_imp_for_\(.*\)/default_imp_for_\1\n\tconnectors::${payment_gateway_camelcase},/" crates/hyperswitch_connectors/src/default_implementations.rs sed -i'' -e "s/^default_imp_for_\(.*\)/default_imp_for_\1\n\tconnectors::${payment_gateway_camelcase},/" crates/hyperswitch_connectors/src/default_implementations_v2.rs # Remove temporary files created in above step -rm $conn.rs-e $src/types/api.rs-e $src/configs/settings.rs-e config/development.toml-e config/docker_compose.toml-e config/config.example.toml-e loadtest/config/development.toml-e crates/api_models/src/enums.rs-e crates/euclid/src/enums.rs-e crates/api_models/src/routing.rs-e $src/core/payments/flows.rs-e crates/common_enums/src/enums.rs-e $src/types/transformers.rs-e $src/core/admin.rs-e +rm $conn.rs-e $src/types/api.rs-e $src/configs/settings.rs-e config/development.toml-e config/docker_compose.toml-e config/config.example.toml-e loadtest/config/development.toml-e crates/api_models/src/connector_enums.rs-e crates/euclid/src/enums.rs-e crates/api_models/src/routing.rs-e $src/core/payments/flows.rs-e crates/common_enums/src/connector_enums.rs-e $src/types/transformers.rs-e $src/core/admin.rs-e crates/hyperswitch_connectors/src/default_implementations.rs-e crates/hyperswitch_connectors/src/default_implementations_v2.rs-e crates/hyperswitch_interfaces/src/configs.rs-e $src/connector.rs-e cd $conn/ # Generate template files for the connector diff --git a/scripts/execute_cypress.sh b/scripts/execute_cypress.sh new file mode 100755 index 000000000000..1f1219ee717c --- /dev/null +++ b/scripts/execute_cypress.sh @@ -0,0 +1,188 @@ +#! /usr/bin/env bash + +set -euo pipefail + +# Initialize tmp_file globally +tmp_file="" + +# Define arrays for services, etc. +# Read service arrays from environment variables +read -r -a payments <<< "${PAYMENTS_CONNECTORS[@]:-}" +read -r -a payouts <<< "${PAYOUTS_CONNECTORS[@]:-}" +read -r -a payment_method_list <<< "${PAYMENT_METHOD_LIST[@]:-}" +read -r -a routing <<< "${ROUTING[@]:-}" + +# Define arrays +connector_map=() +failed_connectors=() + +# Define an associative array to map environment variables to service names +declare -A services=( + ["PAYMENTS_CONNECTORS"]="payments" + ["PAYOUTS_CONNECTORS"]="payouts" + ["PAYMENT_METHOD_LIST"]="payment_method_list" + ["ROUTING"]="routing" +) + +# Function to print messages in color +function print_color() { + # Input params + local color="$1" + local message="$2" + + # Define colors + local reset='\033[0m' + local red='\033[0;31m' + local green='\033[0;32m' + local yellow='\033[0;33m' + + # Use indirect reference to get the color value + echo -e "${!color}${message}${reset}" +} +export -f print_color + +# Function to check if a command exists +function command_exists() { + command -v "$1" > /dev/null 2>&1 +} + +# Function to read service arrays from environment variables +function read_service_arrays() { + # Loop through the associative array and check if each service is exported + for var in "${!services[@]}"; do + if [[ -n "${!var+x}" ]]; then + connector_map+=("${services[$var]}") + else + print_color "yellow" "Environment variable ${var} is not set. Skipping..." + fi + done +} + +# Function to execute Cypress tests +function execute_test() { + if [[ $# -lt 3 ]]; then + print_color "red" "ERROR: Insufficient arguments provided to execute_test." + exit 1 + fi + + local connector="$1" + local service="$2" + local tmp_file="$3" + + print_color "yellow" "Executing tests for ${service} with connector ${connector}..." + + export REPORT_NAME="${service}_${connector}_report" + + if ! CYPRESS_CONNECTOR="$connector" npm run "cypress:$service"; then + echo "${service}-${connector}" >> "${tmp_file}" + fi +} +export -f execute_test + +# Function to run tests +function run_tests() { + local jobs="${1:-1}" + tmp_file=$(mktemp) + + # Ensure temporary file is removed on script exit + trap 'cleanup' EXIT + + for service in "${connector_map[@]}"; do + declare -n connectors="$service" + + if [[ ${#connectors[@]} -eq 0 ]]; then + # Service-level test (e.g., payment-method-list or routing) + [[ $service == "payment_method_list" ]] && service="payment-method-list" + + echo "Running ${service} tests without connectors..." + export REPORT_NAME="${service}_report" + + if ! npm run "cypress:${service}"; then + echo "${service}" >> "${tmp_file}" + fi + else + # Connector-specific tests (e.g., payments or payouts) + print_color "yellow" "Running tests for service: '${service}' with connectors: [${connectors[*]}] in batches of ${jobs}..." + + # Execute tests in parallel + printf '%s\n' "${connectors[@]}" | parallel --jobs "${jobs}" execute_test {} "${service}" "${tmp_file}" + fi + done + + # Collect failed connectors + if [[ -s "${tmp_file}" ]]; then + failed_connectors=($(< "${tmp_file}")) + print_color "red" "One or more connectors failed to run:" + printf '%s\n' "${failed_connectors[@]}" + exit 1 + else + print_color "green" "Cypress tests execution successful!" + fi +} + +# Function to check and install dependencies +function check_dependencies() { + # parallel and npm are mandatory dependencies. exit the script if not found. + local dependencies=("parallel" "npm") + + for cmd in "${dependencies[@]}"; do + if ! command_exists "$cmd"; then + print_color "red" "ERROR: ${cmd^} is not installed!" + exit 1 + else + print_color "green" "${cmd^} is installed already!" + + if [[ ${cmd} == "npm" ]]; then + npm ci || { + print_color "red" "Command \`npm ci\` failed!" + exit 1 + } + fi + fi + done +} + +# Cleanup function to handle exit +function cleanup() { + print_color "yellow" "Cleaning up..." + if [[ -d "cypress-tests" ]]; then + cd - + fi + + if [[ -n "${tmp_file}" && -f "${tmp_file}" ]]; then + rm -f "${tmp_file}" + fi +} + +# Main function +function main() { + local command="${1:-}" + local jobs="${2:-5}" + + # Ensure script runs from 'cypress-tests' directory + if [[ "$(basename "$PWD")" != "cypress-tests" ]]; then + print_color "yellow" "Changing directory to 'cypress-tests'..." + cd cypress-tests || { + print_color "red" "ERROR: Directory 'cypress-tests' not found!" + exit 1 + } + fi + + check_dependencies + read_service_arrays + + case "$command" in + --parallel | -p) + print_color "yellow" "WARNING: Running Cypress tests in parallel is more resource-intensive!" + # At present, parallel execution is restricted to not run out of memory + # But can be scaled up by passing the value as an argument + run_tests "$jobs" + ;; + *) + run_tests 1 + ;; + esac +} + +# Execute the main function with passed arguments +main "$@" diff --git a/v2_migrations/2024-08-23-112510_payment_methods_v2_db_changes/down.sql b/v2_migrations/2024-08-23-112510_payment_methods_v2_db_changes/down.sql index cde77d754f88..30c499e6c42d 100644 --- a/v2_migrations/2024-08-23-112510_payment_methods_v2_db_changes/down.sql +++ b/v2_migrations/2024-08-23-112510_payment_methods_v2_db_changes/down.sql @@ -1,23 +1,18 @@ -- This file should undo anything in `up.sql` -ALTER TABLE payment_methods ADD COLUMN IF NOT EXISTS accepted_currency "Currency"[]; - -ALTER TABLE payment_methods ADD COLUMN IF NOT EXISTS scheme VARCHAR(32); - -ALTER TABLE payment_methods ADD COLUMN IF NOT EXISTS token VARCHAR(128); - -ALTER TABLE payment_methods ADD COLUMN IF NOT EXISTS cardholder_name VARCHAR(255); - -ALTER TABLE payment_methods ADD COLUMN IF NOT EXISTS issuer_name VARCHAR(64); - -ALTER TABLE payment_methods ADD COLUMN IF NOT EXISTS issuer_country VARCHAR(64); - -ALTER TABLE payment_methods ADD COLUMN IF NOT EXISTS is_stored BOOLEAN; - -ALTER TABLE payment_methods ADD COLUMN IF NOT EXISTS direct_debit_token VARCHAR(128); - -ALTER TABLE payment_methods ADD COLUMN IF NOT EXISTS swift_code VARCHAR(32); - -ALTER TABLE payment_methods ADD COLUMN IF NOT EXISTS payment_method_issuer VARCHAR(128); +ALTER TABLE payment_methods + ADD COLUMN IF NOT EXISTS accepted_currency "Currency" [ ], + ADD COLUMN IF NOT EXISTS scheme VARCHAR(32), + ADD COLUMN IF NOT EXISTS token VARCHAR(128), + ADD COLUMN IF NOT EXISTS cardholder_name VARCHAR(255), + ADD COLUMN IF NOT EXISTS issuer_name VARCHAR(64), + ADD COLUMN IF NOT EXISTS issuer_country VARCHAR(64), + ADD COLUMN IF NOT EXISTS is_stored BOOLEAN, + ADD COLUMN IF NOT EXISTS direct_debit_token VARCHAR(128), + ADD COLUMN IF NOT EXISTS swift_code VARCHAR(32), + ADD COLUMN IF NOT EXISTS payment_method_issuer VARCHAR(128), + ADD COLUMN IF NOT EXISTS metadata JSON, + ADD COLUMN IF NOT EXISTS payment_method VARCHAR, + ADD COLUMN IF NOT EXISTS payment_method_type VARCHAR(64); CREATE TYPE "PaymentMethodIssuerCode" AS ENUM ( 'jp_hdfc', @@ -34,11 +29,14 @@ CREATE TYPE "PaymentMethodIssuerCode" AS ENUM ( ALTER TABLE payment_methods ADD COLUMN IF NOT EXISTS payment_method_issuer_code "PaymentMethodIssuerCode"; -ALTER TABLE payment_methods DROP COLUMN IF EXISTS locker_fingerprint_id; +ALTER TABLE payment_methods + DROP COLUMN IF EXISTS locker_fingerprint_id, + DROP COLUMN IF EXISTS payment_method_type_v2, + DROP COLUMN IF EXISTS payment_method_subtype; ALTER TABLE payment_methods ADD COLUMN IF NOT EXISTS payment_method_id VARCHAR(64); UPDATE payment_methods SET payment_method_id = id; ALTER TABLE payment_methods DROP CONSTRAINT IF EXISTS payment_methods_pkey; ALTER TABLE payment_methods ADD CONSTRAINT payment_methods_pkey PRIMARY KEY (payment_method_id); ALTER TABLE payment_methods DROP COLUMN IF EXISTS id; -ALTER TABLE payment_methods ADD COLUMN IF NOT EXISTS id SERIAL; \ No newline at end of file +ALTER TABLE payment_methods ADD COLUMN IF NOT EXISTS id SERIAL; diff --git a/v2_migrations/2024-08-23-112510_payment_methods_v2_db_changes/up.sql b/v2_migrations/2024-08-23-112510_payment_methods_v2_db_changes/up.sql index 151cf068cff2..fb319d9f05df 100644 --- a/v2_migrations/2024-08-23-112510_payment_methods_v2_db_changes/up.sql +++ b/v2_migrations/2024-08-23-112510_payment_methods_v2_db_changes/up.sql @@ -1,35 +1,31 @@ -- Your SQL goes here -ALTER TABLE payment_methods DROP COLUMN IF EXISTS accepted_currency; - -ALTER TABLE payment_methods DROP COLUMN IF EXISTS scheme; - -ALTER TABLE payment_methods DROP COLUMN IF EXISTS token; - -ALTER TABLE payment_methods DROP COLUMN IF EXISTS cardholder_name; - -ALTER TABLE payment_methods DROP COLUMN IF EXISTS issuer_name; - -ALTER TABLE payment_methods DROP COLUMN IF EXISTS issuer_country; - -ALTER TABLE payment_methods DROP COLUMN IF EXISTS payer_country; - -ALTER TABLE payment_methods DROP COLUMN IF EXISTS is_stored; - -ALTER TABLE payment_methods DROP COLUMN IF EXISTS direct_debit_token; - -ALTER TABLE payment_methods DROP COLUMN IF EXISTS swift_code; - -ALTER TABLE payment_methods DROP COLUMN IF EXISTS payment_method_issuer; - -ALTER TABLE payment_methods DROP COLUMN IF EXISTS payment_method_issuer_code; +ALTER TABLE payment_methods + DROP COLUMN IF EXISTS accepted_currency, + DROP COLUMN IF EXISTS scheme, + DROP COLUMN IF EXISTS token, + DROP COLUMN IF EXISTS cardholder_name, + DROP COLUMN IF EXISTS issuer_name, + DROP COLUMN IF EXISTS issuer_country, + DROP COLUMN IF EXISTS payer_country, + DROP COLUMN IF EXISTS is_stored, + DROP COLUMN IF EXISTS direct_debit_token, + DROP COLUMN IF EXISTS swift_code, + DROP COLUMN IF EXISTS payment_method_issuer, + DROP COLUMN IF EXISTS payment_method_issuer_code, + DROP COLUMN IF EXISTS metadata, + DROP COLUMN IF EXISTS payment_method, + DROP COLUMN IF EXISTS payment_method_type; DROP TYPE IF EXISTS "PaymentMethodIssuerCode"; -ALTER TABLE payment_methods ADD COLUMN IF NOT EXISTS locker_fingerprint_id VARCHAR(64); +ALTER TABLE payment_methods + ADD COLUMN IF NOT EXISTS locker_fingerprint_id VARCHAR(64), + ADD COLUMN IF NOT EXISTS payment_method_type_v2 VARCHAR(64), + ADD COLUMN IF NOT EXISTS payment_method_subtype VARCHAR(64); ALTER TABLE payment_methods DROP COLUMN IF EXISTS id; ALTER TABLE payment_methods ADD COLUMN IF NOT EXISTS id VARCHAR(64); UPDATE payment_methods SET id = payment_method_id; ALTER TABLE payment_methods DROP CONSTRAINT IF EXISTS payment_methods_pkey; ALTER TABLE payment_methods ADD CONSTRAINT payment_methods_pkey PRIMARY KEY (id); -ALTER TABLE payment_methods DROP COLUMN IF EXISTS payment_method_id; \ No newline at end of file +ALTER TABLE payment_methods DROP COLUMN IF EXISTS payment_method_id; diff --git a/v2_migrations/2024-08-28-081721_add_v2_columns/down.sql b/v2_migrations/2024-08-28-081721_add_v2_columns/down.sql index cfc769e70e12..a4616016abfc 100644 --- a/v2_migrations/2024-08-28-081721_add_v2_columns/down.sql +++ b/v2_migrations/2024-08-28-081721_add_v2_columns/down.sql @@ -10,7 +10,8 @@ ALTER TABLE business_profile DROP COLUMN routing_algorithm_id, DROP COLUMN order_fulfillment_time_origin, DROP COLUMN frm_routing_algorithm_id, DROP COLUMN payout_routing_algorithm_id, - DROP COLUMN default_fallback_routing; + DROP COLUMN default_fallback_routing, + DROP COLUMN should_collect_cvv_during_payment; DROP TYPE "OrderFulfillmentTimeOrigin"; @@ -38,4 +39,10 @@ ALTER TABLE payment_attempt DROP COLUMN payment_method_type_v2, DROP COLUMN routing_result, DROP COLUMN authentication_applied, DROP COLUMN external_reference_id, - DROP COLUMN tax_on_surcharge; + DROP COLUMN tax_on_surcharge, + DROP COLUMN payment_method_billing_address, + DROP COLUMN redirection_data, + DROP COLUMN connector_payment_data; + +ALTER TABLE merchant_connector_account +ALTER COLUMN payment_methods_enabled TYPE JSON [ ]; diff --git a/v2_migrations/2024-08-28-081721_add_v2_columns/up.sql b/v2_migrations/2024-08-28-081721_add_v2_columns/up.sql index 520bbdf6e7e1..faebb36cdf9d 100644 --- a/v2_migrations/2024-08-28-081721_add_v2_columns/up.sql +++ b/v2_migrations/2024-08-28-081721_add_v2_columns/up.sql @@ -14,7 +14,14 @@ ADD COLUMN routing_algorithm_id VARCHAR(64) DEFAULT NULL, ADD COLUMN order_fulfillment_time_origin "OrderFulfillmentTimeOrigin" DEFAULT NULL, ADD COLUMN frm_routing_algorithm_id VARCHAR(64) DEFAULT NULL, ADD COLUMN payout_routing_algorithm_id VARCHAR(64) DEFAULT NULL, - ADD COLUMN default_fallback_routing JSONB DEFAULT NULL; + ADD COLUMN default_fallback_routing JSONB DEFAULT NULL, + -- Intentionally not adding a default value here since we would have to + -- check if any merchants have enabled this from configs table, + -- before filling data for this column. + -- If no merchants have enabled this, then we can use `false` as the default value + -- when adding the column, later we can drop the default added for the column + -- so that we ensure new records inserted always have a value for the column. +ADD COLUMN should_collect_cvv_during_payment BOOLEAN NOT NULL; ALTER TABLE payment_intent ADD COLUMN merchant_reference_id VARCHAR(64), @@ -41,4 +48,11 @@ ADD COLUMN payment_method_type_v2 VARCHAR, ADD COLUMN routing_result JSONB, ADD COLUMN authentication_applied "AuthenticationType", ADD COLUMN external_reference_id VARCHAR(128), - ADD COLUMN tax_on_surcharge BIGINT; + ADD COLUMN tax_on_surcharge BIGINT, + ADD COLUMN payment_method_billing_address BYTEA, + ADD COLUMN redirection_data JSONB, + ADD COLUMN connector_payment_data VARCHAR(512); + +-- Change the type of the column from JSON to JSONB +ALTER TABLE merchant_connector_account +ALTER COLUMN payment_methods_enabled TYPE JSONB [ ]; diff --git a/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/down.sql b/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/down.sql index 33a58538a8af..661f7f294e0d 100644 --- a/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/down.sql +++ b/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/down.sql @@ -87,6 +87,17 @@ ALTER COLUMN currency DROP NOT NULL, ALTER COLUMN client_secret DROP NOT NULL, ALTER COLUMN profile_id DROP NOT NULL; +ALTER TABLE payment_intent +ALTER COLUMN active_attempt_id +SET NOT NULL; + +ALTER TABLE payment_intent +ALTER COLUMN session_expiry DROP NOT NULL; + +ALTER TABLE payment_intent +ALTER COLUMN active_attempt_id +SET DEFAULT 'xxx'; + ------------------------ Payment Attempt ----------------------- ALTER TABLE payment_attempt DROP CONSTRAINT payment_attempt_pkey; @@ -94,5 +105,8 @@ UPDATE payment_attempt SET attempt_id = id WHERE attempt_id IS NULL; +ALTER TABLE payment_attempt +ALTER COLUMN net_amount DROP NOT NULL; + ALTER TABLE payment_attempt ADD PRIMARY KEY (attempt_id, merchant_id); diff --git a/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/up.sql b/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/up.sql index 33718e205007..3b1fe9b46afa 100644 --- a/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/up.sql +++ b/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/up.sql @@ -104,4 +104,35 @@ SET NOT NULL, ALTER COLUMN currency SET NOT NULL, ALTER COLUMN client_secret +SET NOT NULL, + ALTER COLUMN session_expiry +SET NOT NULL, + ALTER COLUMN active_attempt_id DROP NOT NULL; + +------------------------ Payment Attempt ----------------------- +ALTER TABLE payment_attempt DROP CONSTRAINT payment_attempt_pkey; + +ALTER TABLE payment_attempt +ADD PRIMARY KEY (id); + +-- This migration is to make fields mandatory in payment_attempt table +ALTER TABLE payment_attempt +ALTER COLUMN net_amount +SET NOT NULL, + ALTER COLUMN authentication_type +SET NOT NULL, + ALTER COLUMN payment_method_type_v2 +SET NOT NULL, + ALTER COLUMN payment_method_subtype +SET NOT NULL; + +ALTER TABLE payment_intent +ALTER COLUMN session_expiry SET NOT NULL; + +-- This migration is to make fields optional in payment_intent table +ALTER TABLE payment_intent +ALTER COLUMN active_attempt_id DROP NOT NULL; + +ALTER TABLE payment_intent +ALTER COLUMN active_attempt_id DROP DEFAULT; diff --git a/v2_migrations/2024-08-28-081847_drop_v1_columns/down.sql b/v2_migrations/2024-10-08-081847_drop_v1_columns/down.sql similarity index 94% rename from v2_migrations/2024-08-28-081847_drop_v1_columns/down.sql rename to v2_migrations/2024-10-08-081847_drop_v1_columns/down.sql index 65e90b126fb9..64cbd2233eac 100644 --- a/v2_migrations/2024-08-28-081847_drop_v1_columns/down.sql +++ b/v2_migrations/2024-10-08-081847_drop_v1_columns/down.sql @@ -76,6 +76,7 @@ ADD COLUMN IF NOT EXISTS attempt_id VARCHAR(64) NOT NULL, ADD COLUMN offer_amount bigint, ADD COLUMN payment_method VARCHAR, ADD COLUMN connector_transaction_id VARCHAR(64), + ADD COLUMN connector_transaction_data VARCHAR(512), ADD COLUMN capture_method "CaptureMethod", ADD COLUMN capture_on TIMESTAMP, ADD COLUMN mandate_id VARCHAR(64), @@ -84,7 +85,10 @@ ADD COLUMN IF NOT EXISTS attempt_id VARCHAR(64) NOT NULL, ADD COLUMN mandate_details JSONB, ADD COLUMN mandate_data JSONB, ADD COLUMN tax_amount bigint, - ADD COLUMN straight_through_algorithm JSONB; + ADD COLUMN straight_through_algorithm JSONB, + ADD COLUMN confirm BOOLEAN, + ADD COLUMN authentication_data JSONB, + ADD COLUMN payment_method_billing_address_id VARCHAR(64); -- Create the index which was dropped because of dropping the column CREATE INDEX payment_attempt_connector_transaction_id_merchant_id_index ON payment_attempt (connector_transaction_id, merchant_id); diff --git a/v2_migrations/2024-08-28-081847_drop_v1_columns/up.sql b/v2_migrations/2024-10-08-081847_drop_v1_columns/up.sql similarity index 91% rename from v2_migrations/2024-08-28-081847_drop_v1_columns/up.sql rename to v2_migrations/2024-10-08-081847_drop_v1_columns/up.sql index 55b0b19d0b41..276bea10bd51 100644 --- a/v2_migrations/2024-08-28-081847_drop_v1_columns/up.sql +++ b/v2_migrations/2024-10-08-081847_drop_v1_columns/up.sql @@ -33,7 +33,7 @@ ALTER TABLE business_profile DROP COLUMN profile_id, DROP COLUMN frm_routing_algorithm, DROP COLUMN payout_routing_algorithm; --- This migration is to remove the business_country, business_label, business_sub_label, test_mode, merchant_connector_id and frm_configs columns from the merchant_connector_account table +-- This migration is to remove the fields that are no longer used by the v1 application, or some type changes. ALTER TABLE merchant_connector_account DROP COLUMN IF EXISTS business_country, DROP COLUMN IF EXISTS business_label, DROP COLUMN IF EXISTS business_sub_label, @@ -74,6 +74,7 @@ ALTER TABLE payment_attempt DROP COLUMN attempt_id, DROP COLUMN offer_amount, DROP COLUMN payment_method, DROP COLUMN connector_transaction_id, + DROP COLUMN connector_transaction_data, DROP COLUMN capture_method, DROP COLUMN capture_on, DROP COLUMN mandate_id, @@ -82,4 +83,7 @@ ALTER TABLE payment_attempt DROP COLUMN attempt_id, DROP COLUMN mandate_details, DROP COLUMN mandate_data, DROP COLUMN tax_amount, - DROP COLUMN straight_through_algorithm; + DROP COLUMN straight_through_algorithm, + DROP COLUMN confirm, + DROP COLUMN authentication_data, + DROP COLUMN payment_method_billing_address_id;